606 lines
18 KiB
Go
606 lines
18 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type ManagedServer struct {
|
|
ID int
|
|
Name string
|
|
BaseURL string
|
|
AdminUsername string
|
|
AdminKey string
|
|
EnableSSH bool
|
|
EnableXray bool
|
|
IsActive bool
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
}
|
|
|
|
type ManagedServerDTO struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
BaseURL string `json:"base_url"`
|
|
AdminUsername string `json:"admin_username,omitempty"`
|
|
EnableSSH bool `json:"enable_ssh"`
|
|
EnableXray bool `json:"enable_xray"`
|
|
IsActive bool `json:"is_active"`
|
|
IsLocal bool `json:"is_local"`
|
|
CreatedAt time.Time `json:"created_at,omitempty"`
|
|
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
|
}
|
|
|
|
type ManagedServerPayload struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
BaseURL string `json:"base_url"`
|
|
AdminUsername string `json:"admin_username"`
|
|
AdminKey string `json:"admin_key"`
|
|
EnableSSH bool `json:"enable_ssh"`
|
|
EnableXray bool `json:"enable_xray"`
|
|
IsActive bool `json:"is_active"`
|
|
}
|
|
|
|
func (s *Store) EnsureManagedServersSchema(ctx context.Context) error {
|
|
_, err := s.db.ExecContext(ctx, `
|
|
CREATE TABLE IF NOT EXISTS managed_servers (
|
|
id SERIAL PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
base_url TEXT NOT NULL UNIQUE,
|
|
admin_username TEXT NOT NULL DEFAULT 'admin',
|
|
admin_key TEXT NOT NULL DEFAULT '',
|
|
enable_ssh BOOLEAN NOT NULL DEFAULT TRUE,
|
|
enable_xray BOOLEAN NOT NULL DEFAULT TRUE,
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
)`)
|
|
return err
|
|
}
|
|
|
|
func (s *Store) ListManagedServers(ctx context.Context) ([]*ManagedServer, error) {
|
|
rows, err := s.db.QueryContext(ctx, `
|
|
SELECT id, name, base_url, admin_username, admin_key, enable_ssh, enable_xray, is_active, created_at, updated_at
|
|
FROM managed_servers ORDER BY id ASC`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var out []*ManagedServer
|
|
for rows.Next() {
|
|
ms := &ManagedServer{}
|
|
if err := rows.Scan(&ms.ID, &ms.Name, &ms.BaseURL, &ms.AdminUsername, &ms.AdminKey, &ms.EnableSSH, &ms.EnableXray, &ms.IsActive, &ms.CreatedAt, &ms.UpdatedAt); err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, ms)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
func (s *Store) GetManagedServer(ctx context.Context, id int) (*ManagedServer, error) {
|
|
ms := &ManagedServer{}
|
|
err := s.db.QueryRowContext(ctx, `
|
|
SELECT id, name, base_url, admin_username, admin_key, enable_ssh, enable_xray, is_active, created_at, updated_at
|
|
FROM managed_servers WHERE id=$1`, id).
|
|
Scan(&ms.ID, &ms.Name, &ms.BaseURL, &ms.AdminUsername, &ms.AdminKey, &ms.EnableSSH, &ms.EnableXray, &ms.IsActive, &ms.CreatedAt, &ms.UpdatedAt)
|
|
if err == sql.ErrNoRows {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ms, nil
|
|
}
|
|
|
|
func (s *Store) UpsertManagedServer(ctx context.Context, p ManagedServerPayload) (*ManagedServer, error) {
|
|
name := strings.TrimSpace(p.Name)
|
|
baseURL := normalizeManagedServerBaseURL(p.BaseURL)
|
|
adminUsername := strings.TrimSpace(p.AdminUsername)
|
|
if adminUsername == "" {
|
|
adminUsername = "admin"
|
|
}
|
|
if name == "" {
|
|
return nil, fmt.Errorf("server name required")
|
|
}
|
|
if baseURL == "" {
|
|
return nil, fmt.Errorf("base url required")
|
|
}
|
|
if p.ID != "" && p.ID != "local" {
|
|
id, err := strconv.Atoi(p.ID)
|
|
if err != nil || id <= 0 {
|
|
return nil, fmt.Errorf("invalid server id")
|
|
}
|
|
if strings.TrimSpace(p.AdminKey) == "" {
|
|
_, err = s.db.ExecContext(ctx, `
|
|
UPDATE managed_servers
|
|
SET name=$2, base_url=$3, admin_username=$4, enable_ssh=$5, enable_xray=$6, is_active=$7, updated_at=NOW()
|
|
WHERE id=$1`, id, name, baseURL, adminUsername, p.EnableSSH, p.EnableXray, p.IsActive)
|
|
} else {
|
|
_, err = s.db.ExecContext(ctx, `
|
|
UPDATE managed_servers
|
|
SET name=$2, base_url=$3, admin_username=$4, admin_key=$5, enable_ssh=$6, enable_xray=$7, is_active=$8, updated_at=NOW()
|
|
WHERE id=$1`, id, name, baseURL, adminUsername, p.AdminKey, p.EnableSSH, p.EnableXray, p.IsActive)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s.GetManagedServer(ctx, id)
|
|
}
|
|
if strings.TrimSpace(p.AdminKey) == "" {
|
|
return nil, fmt.Errorf("admin key/password required")
|
|
}
|
|
var id int
|
|
err := s.db.QueryRowContext(ctx, `
|
|
INSERT INTO managed_servers (name, base_url, admin_username, admin_key, enable_ssh, enable_xray, is_active)
|
|
VALUES ($1,$2,$3,$4,$5,$6,$7)
|
|
ON CONFLICT (base_url) DO UPDATE SET
|
|
name=EXCLUDED.name,
|
|
admin_username=EXCLUDED.admin_username,
|
|
admin_key=EXCLUDED.admin_key,
|
|
enable_ssh=EXCLUDED.enable_ssh,
|
|
enable_xray=EXCLUDED.enable_xray,
|
|
is_active=EXCLUDED.is_active,
|
|
updated_at=NOW()
|
|
RETURNING id`, name, baseURL, adminUsername, p.AdminKey, p.EnableSSH, p.EnableXray, p.IsActive).Scan(&id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s.GetManagedServer(ctx, id)
|
|
}
|
|
|
|
func (s *Store) DeleteManagedServer(ctx context.Context, id int) error {
|
|
_, err := s.db.ExecContext(ctx, `DELETE FROM managed_servers WHERE id=$1`, id)
|
|
return err
|
|
}
|
|
|
|
func managedServerToDTO(ms *ManagedServer) ManagedServerDTO {
|
|
return ManagedServerDTO{
|
|
ID: strconv.Itoa(ms.ID),
|
|
Name: ms.Name,
|
|
BaseURL: ms.BaseURL,
|
|
AdminUsername: ms.AdminUsername,
|
|
EnableSSH: ms.EnableSSH,
|
|
EnableXray: ms.EnableXray,
|
|
IsActive: ms.IsActive,
|
|
CreatedAt: ms.CreatedAt,
|
|
UpdatedAt: ms.UpdatedAt,
|
|
}
|
|
}
|
|
|
|
func localManagedServerDTO() ManagedServerDTO {
|
|
cfg := getGlobalCfg()
|
|
xrayEnabled := cfg != nil && cfg.Xray != nil && cfg.Xray.Enabled
|
|
return ManagedServerDTO{
|
|
ID: "local",
|
|
Name: "Master node",
|
|
BaseURL: "local",
|
|
EnableSSH: true,
|
|
EnableXray: xrayEnabled,
|
|
IsActive: true,
|
|
IsLocal: true,
|
|
}
|
|
}
|
|
|
|
func normalizeManagedServerBaseURL(raw string) string {
|
|
raw = strings.TrimSpace(raw)
|
|
if raw == "" {
|
|
return ""
|
|
}
|
|
if !strings.HasPrefix(raw, "http://") && !strings.HasPrefix(raw, "https://") {
|
|
raw = "http://" + raw
|
|
}
|
|
u, err := url.Parse(raw)
|
|
if err != nil || u.Scheme == "" || u.Host == "" {
|
|
return ""
|
|
}
|
|
u.Path = strings.TrimRight(u.Path, "/")
|
|
u.RawQuery = ""
|
|
u.Fragment = ""
|
|
return strings.TrimRight(u.String(), "/")
|
|
}
|
|
|
|
func requestedServerID(r *http.Request) string {
|
|
id := strings.TrimSpace(r.URL.Query().Get("server_id"))
|
|
if id == "" {
|
|
id = strings.TrimSpace(r.URL.Query().Get("server"))
|
|
}
|
|
return id
|
|
}
|
|
|
|
func managedServerFromID(ctx context.Context, store *Store, id string) (*ManagedServer, bool, error) {
|
|
id = strings.TrimSpace(id)
|
|
if id == "" || id == "local" || id == "0" {
|
|
return nil, false, nil
|
|
}
|
|
if store == nil {
|
|
return nil, false, fmt.Errorf("database not configured")
|
|
}
|
|
n, err := strconv.Atoi(id)
|
|
if err != nil || n <= 0 {
|
|
return nil, false, fmt.Errorf("invalid server id")
|
|
}
|
|
ms, err := store.GetManagedServer(ctx, n)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
if ms == nil {
|
|
return nil, false, fmt.Errorf("server not found")
|
|
}
|
|
if !ms.IsActive {
|
|
return nil, false, fmt.Errorf("server is disabled")
|
|
}
|
|
return ms, true, nil
|
|
}
|
|
|
|
func remoteLoginToken(ctx context.Context, ms *ManagedServer) (string, error) {
|
|
body, _ := json.Marshal(map[string]string{"username": ms.AdminUsername, "password": ms.AdminKey})
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, ms.BaseURL+"/api/auth/login", bytes.NewReader(body))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
client := &http.Client{Timeout: 15 * time.Second}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
data, _ := io.ReadAll(io.LimitReader(resp.Body, 128*1024))
|
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
return "", fmt.Errorf("remote login failed: %s", strings.TrimSpace(string(data)))
|
|
}
|
|
var out struct {
|
|
Token string `json:"token"`
|
|
}
|
|
if err := json.Unmarshal(data, &out); err != nil || out.Token == "" {
|
|
return "", fmt.Errorf("remote login returned no token")
|
|
}
|
|
return out.Token, nil
|
|
}
|
|
|
|
func proxyManagedServer(ctx context.Context, ms *ManagedServer, method, path string, body []byte, contentType string) (int, []byte, string, error) {
|
|
token, err := remoteLoginToken(ctx, ms)
|
|
if err != nil {
|
|
return 0, nil, "", err
|
|
}
|
|
if !strings.HasPrefix(path, "/") {
|
|
path = "/" + path
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, method, ms.BaseURL+path, bytes.NewReader(body))
|
|
if err != nil {
|
|
return 0, nil, "", err
|
|
}
|
|
if contentType == "" {
|
|
contentType = "application/json"
|
|
}
|
|
req.Header.Set("Content-Type", contentType)
|
|
req.Header.Set("X-Session-Token", token)
|
|
client := &http.Client{Timeout: 30 * time.Second}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return 0, nil, "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
data, _ := io.ReadAll(io.LimitReader(resp.Body, 2*1024*1024))
|
|
return resp.StatusCode, data, resp.Header.Get("Content-Type"), nil
|
|
}
|
|
|
|
func handleManagedProxyOrLocal(store *Store, local http.HandlerFunc) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
if proxyManagedServerFromRequest(w, r, store, "", nil, "") {
|
|
return
|
|
}
|
|
local(w, r)
|
|
}
|
|
}
|
|
|
|
func writeProxyResponse(w http.ResponseWriter, status int, body []byte, contentType string) {
|
|
if contentType != "" {
|
|
w.Header().Set("Content-Type", contentType)
|
|
}
|
|
if status == 0 {
|
|
status = http.StatusBadGateway
|
|
}
|
|
w.WriteHeader(status)
|
|
if len(body) > 0 {
|
|
_, _ = w.Write(body)
|
|
}
|
|
}
|
|
|
|
func proxyManagedServerFromRequest(w http.ResponseWriter, r *http.Request, store *Store, remotePath string, body []byte, filterOwner string) bool {
|
|
ms, remote, err := managedServerFromID(r.Context(), store, requestedServerID(r))
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return true
|
|
}
|
|
if !remote {
|
|
return false
|
|
}
|
|
if remotePath == "" {
|
|
remotePath = r.URL.Path
|
|
if r.URL.RawQuery != "" {
|
|
q := r.URL.Query()
|
|
q.Del("server_id")
|
|
q.Del("server")
|
|
if enc := q.Encode(); enc != "" {
|
|
remotePath += "?" + enc
|
|
}
|
|
}
|
|
}
|
|
if body == nil && r.Body != nil && r.Method != http.MethodGet {
|
|
body, _ = io.ReadAll(io.LimitReader(r.Body, 2*1024*1024))
|
|
}
|
|
status, data, ct, err := proxyManagedServer(r.Context(), ms, r.Method, remotePath, body, r.Header.Get("Content-Type"))
|
|
if err != nil {
|
|
http.Error(w, "remote server error: "+err.Error(), http.StatusBadGateway)
|
|
return true
|
|
}
|
|
if status >= 200 && status < 300 && filterOwner != "" && strings.Contains(ct, "json") {
|
|
if filtered, ok := filterRemoteOwnerJSON(remotePath, data, filterOwner); ok {
|
|
data = filtered
|
|
}
|
|
}
|
|
writeProxyResponse(w, status, data, ct)
|
|
return true
|
|
}
|
|
|
|
func filterRemoteOwnerJSON(path string, data []byte, owner string) ([]byte, bool) {
|
|
if owner == "" || len(data) == 0 {
|
|
return data, false
|
|
}
|
|
if strings.HasPrefix(path, "/api/users") {
|
|
var rows []map[string]interface{}
|
|
if err := json.Unmarshal(data, &rows); err != nil {
|
|
return data, false
|
|
}
|
|
out := rows[:0]
|
|
for _, row := range rows {
|
|
if strings.TrimSpace(fmt.Sprint(row["owner_username"])) == owner {
|
|
out = append(out, row)
|
|
}
|
|
}
|
|
filtered, _ := json.Marshal(out)
|
|
return filtered, true
|
|
}
|
|
if strings.HasPrefix(path, "/api/xray/inbounds") {
|
|
var inbounds []map[string]interface{}
|
|
if err := json.Unmarshal(data, &inbounds); err != nil {
|
|
return data, false
|
|
}
|
|
for _, ib := range inbounds {
|
|
clients, _ := ib["clients"].([]interface{})
|
|
filtered := make([]interface{}, 0, len(clients))
|
|
for _, c := range clients {
|
|
m, _ := c.(map[string]interface{})
|
|
if strings.TrimSpace(fmt.Sprint(m["owner_username"])) == owner {
|
|
filtered = append(filtered, c)
|
|
}
|
|
}
|
|
ib["clients"] = filtered
|
|
}
|
|
filtered, _ := json.Marshal(inbounds)
|
|
return filtered, true
|
|
}
|
|
return data, false
|
|
}
|
|
|
|
func handleServers(store *Store) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
if store == nil {
|
|
http.Error(w, "database not configured", http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
sess := sessionFromCtx(r.Context())
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
rows, err := store.ListManagedServers(r.Context())
|
|
if err != nil {
|
|
http.Error(w, "db error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
out := []ManagedServerDTO{localManagedServerDTO()}
|
|
for _, ms := range rows {
|
|
if sess != nil && sess.Role == RoleReseller && !ms.IsActive {
|
|
continue
|
|
}
|
|
dto := managedServerToDTO(ms)
|
|
if sess != nil && sess.Role == RoleReseller {
|
|
dto.AdminUsername = ""
|
|
}
|
|
out = append(out, dto)
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(out)
|
|
case http.MethodPost:
|
|
if sess == nil || sess.Role != RoleSuperAdmin {
|
|
http.Error(w, "forbidden", http.StatusForbidden)
|
|
return
|
|
}
|
|
var p ManagedServerPayload
|
|
if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
|
|
http.Error(w, "invalid json", http.StatusBadRequest)
|
|
return
|
|
}
|
|
ms, err := store.UpsertManagedServer(r.Context(), p)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(managedServerToDTO(ms))
|
|
case http.MethodDelete:
|
|
if sess == nil || sess.Role != RoleSuperAdmin {
|
|
http.Error(w, "forbidden", http.StatusForbidden)
|
|
return
|
|
}
|
|
idStr := strings.TrimSpace(r.URL.Query().Get("id"))
|
|
id, err := strconv.Atoi(idStr)
|
|
if err != nil || id <= 0 {
|
|
http.Error(w, "invalid server id", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if err := store.DeleteManagedServer(r.Context(), id); err != nil {
|
|
http.Error(w, "db error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
default:
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
}
|
|
|
|
func handleServerTest(store *Store) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
if store == nil {
|
|
http.Error(w, "database not configured", http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
var p ManagedServerPayload
|
|
if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
|
|
http.Error(w, "invalid json", http.StatusBadRequest)
|
|
return
|
|
}
|
|
ms := &ManagedServer{Name: p.Name, BaseURL: normalizeManagedServerBaseURL(p.BaseURL), AdminUsername: strings.TrimSpace(p.AdminUsername), AdminKey: p.AdminKey, EnableSSH: p.EnableSSH, EnableXray: p.EnableXray, IsActive: true}
|
|
if p.ID != "" && p.ID != "local" && (ms.BaseURL == "" || ms.AdminKey == "") {
|
|
id, _ := strconv.Atoi(p.ID)
|
|
if id > 0 {
|
|
stored, err := store.GetManagedServer(r.Context(), id)
|
|
if err == nil && stored != nil {
|
|
if ms.BaseURL == "" {
|
|
ms.BaseURL = stored.BaseURL
|
|
}
|
|
if ms.AdminUsername == "" {
|
|
ms.AdminUsername = stored.AdminUsername
|
|
}
|
|
if ms.AdminKey == "" {
|
|
ms.AdminKey = stored.AdminKey
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ms.AdminUsername == "" {
|
|
ms.AdminUsername = "admin"
|
|
}
|
|
if ms.BaseURL == "" || ms.AdminKey == "" {
|
|
http.Error(w, "base url and admin key/password required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
token, err := remoteLoginToken(r.Context(), ms)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadGateway)
|
|
return
|
|
}
|
|
_ = token
|
|
status, data, _, err := proxyManagedServer(r.Context(), ms, http.MethodGet, "/api/auth/me", nil, "application/json")
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadGateway)
|
|
return
|
|
}
|
|
if status < 200 || status >= 300 {
|
|
http.Error(w, strings.TrimSpace(string(data)), http.StatusBadGateway)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{"ok": true, "message": "remote login ok"})
|
|
}
|
|
}
|
|
|
|
func handleManagedServerConfig(store *Store) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
id := requestedServerID(r)
|
|
if id == "" || id == "local" || id == "0" {
|
|
handleServerConfig(w, r)
|
|
return
|
|
}
|
|
if r.Method != http.MethodGet && r.Method != http.MethodPost {
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
body := []byte(nil)
|
|
if r.Method == http.MethodPost {
|
|
var err error
|
|
body, err = io.ReadAll(io.LimitReader(r.Body, 512*1024))
|
|
if err != nil {
|
|
http.Error(w, "failed to read body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
}
|
|
ms, remote, err := managedServerFromID(r.Context(), store, id)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
if !remote {
|
|
handleServerConfig(w, r)
|
|
return
|
|
}
|
|
status, data, ct, err := proxyManagedServer(r.Context(), ms, r.Method, "/api/server/config", body, "application/json")
|
|
if err != nil {
|
|
log.Printf("managed server config proxy %s: %v", ms.BaseURL, err)
|
|
http.Error(w, "remote server error: "+err.Error(), http.StatusBadGateway)
|
|
return
|
|
}
|
|
writeProxyResponse(w, status, data, ct)
|
|
}
|
|
}
|
|
|
|
func remoteSSHUserOwned(ctx context.Context, ms *ManagedServer, username, owner string) bool {
|
|
if owner == "" || username == "" {
|
|
return false
|
|
}
|
|
status, data, _, err := proxyManagedServer(ctx, ms, http.MethodGet, "/api/users", nil, "application/json")
|
|
if err != nil || status < 200 || status >= 300 {
|
|
return false
|
|
}
|
|
var rows []map[string]interface{}
|
|
if err := json.Unmarshal(data, &rows); err != nil {
|
|
return false
|
|
}
|
|
for _, row := range rows {
|
|
if fmt.Sprint(row["username"]) == username && fmt.Sprint(row["owner_username"]) == owner {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func remoteXrayClientOwned(ctx context.Context, ms *ManagedServer, uuid, owner string) bool {
|
|
if owner == "" || uuid == "" {
|
|
return false
|
|
}
|
|
status, data, _, err := proxyManagedServer(ctx, ms, http.MethodGet, "/api/xray/inbounds", nil, "application/json")
|
|
if err != nil || status < 200 || status >= 300 {
|
|
return false
|
|
}
|
|
var inbounds []map[string]interface{}
|
|
if err := json.Unmarshal(data, &inbounds); err != nil {
|
|
return false
|
|
}
|
|
for _, ib := range inbounds {
|
|
clients, _ := ib["clients"].([]interface{})
|
|
for _, c := range clients {
|
|
m, _ := c.(map[string]interface{})
|
|
if fmt.Sprint(m["id"]) == uuid && fmt.Sprint(m["owner_username"]) == owner {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|