diff --git a/go-backend/internal/http/client/federation.go b/go-backend/internal/http/client/federation.go index c57a0d2..e1dd5fd 100644 --- a/go-backend/internal/http/client/federation.go +++ b/go-backend/internal/http/client/federation.go @@ -38,13 +38,16 @@ func NewFederationClient() *FederationClient { } } -func (c *FederationClient) Connect(url, token string) (*RemoteNodeInfo, error) { +func (c *FederationClient) Connect(url, token, localDomain string) (*RemoteNodeInfo, error) { url = strings.TrimSuffix(url, "/") req, err := http.NewRequest("POST", url+"/api/v1/federation/connect", nil) if err != nil { return nil, err } req.Header.Set("Authorization", "Bearer "+token) + if localDomain != "" { + req.Header.Set("X-Panel-Domain", localDomain) + } req.Header.Set("Content-Type", "application/json") resp, err := c.client.Do(req) @@ -73,7 +76,7 @@ func (c *FederationClient) Connect(url, token string) (*RemoteNodeInfo, error) { return &res.Data, nil } -func (c *FederationClient) CreateTunnel(url, token string, protocol string, remotePort int, target string) (*RemoteTunnelResponse, error) { +func (c *FederationClient) CreateTunnel(url, token, localDomain, protocol string, remotePort int, target string) (*RemoteTunnelResponse, error) { url = strings.TrimSuffix(url, "/") payload := map[string]interface{}{ "protocol": protocol, @@ -86,6 +89,9 @@ func (c *FederationClient) CreateTunnel(url, token string, protocol string, remo return nil, err } req.Header.Set("Authorization", "Bearer "+token) + if localDomain != "" { + req.Header.Set("X-Panel-Domain", localDomain) + } req.Header.Set("Content-Type", "application/json") resp, err := c.client.Do(req) diff --git a/go-backend/internal/http/handler/federation.go b/go-backend/internal/http/handler/federation.go index 060b38b..eeb6f3e 100644 --- a/go-backend/internal/http/handler/federation.go +++ b/go-backend/internal/http/handler/federation.go @@ -25,6 +25,7 @@ type createPeerShareRequest struct { ExpiryTime int64 `json:"expiryTime"` PortRangeStart int `json:"portRangeStart"` PortRangeEnd int `json:"portRangeEnd"` + AllowedDomains string `json:"allowedDomains"` } type deletePeerShareRequest struct { @@ -111,6 +112,7 @@ func (h *Handler) federationShareCreate(w http.ResponseWriter, r *http.Request) IsActive: 1, CreatedTime: now, UpdatedTime: now, + AllowedDomains: req.AllowedDomains, } if err := h.repo.CreatePeerShare(share); err != nil { @@ -158,8 +160,14 @@ func (h *Handler) nodeImport(w http.ResponseWriter, r *http.Request) { return } + domainCfg, _ := h.repo.GetConfigByName("panel_domain") + localDomain := "" + if domainCfg != nil { + localDomain = domainCfg.Value + } + fc := client.NewFederationClient() - info, err := fc.Connect(req.RemoteURL, req.Token) + info, err := fc.Connect(req.RemoteURL, req.Token, localDomain) if err != nil { response.WriteJSON(w, response.Err(-2, "Failed to connect: "+err.Error())) return @@ -242,6 +250,26 @@ func (h *Handler) authPeer(next http.HandlerFunc) http.HandlerFunc { return } + if share.AllowedDomains != "" { + clientDomain := r.Header.Get("X-Panel-Domain") + if clientDomain == "" { + response.WriteJSON(w, response.Err(403, "Domain verification required")) + return + } + allowed := false + domains := strings.Split(share.AllowedDomains, ",") + for _, d := range domains { + if strings.TrimSpace(d) == clientDomain { + allowed = true + break + } + } + if !allowed { + response.WriteJSON(w, response.Err(403, "Domain not allowed")) + return + } + } + next(w, r) } } diff --git a/go-backend/internal/http/handler/mutations.go b/go-backend/internal/http/handler/mutations.go index 47bf15b..fcf060f 100644 --- a/go-backend/internal/http/handler/mutations.go +++ b/go-backend/internal/http/handler/mutations.go @@ -543,7 +543,12 @@ func (h *Handler) tunnelCreate(w http.ResponseWriter, r *http.Request) { } if targetPort > 0 && targetAddr != "" { - _, err := fc.CreateTunnel(rUrl.String, rToken.String, targetProto, targetPort, targetAddr) + domainCfg, _ := h.repo.GetConfigByName("panel_domain") + localDomain := "" + if domainCfg != nil { + localDomain = domainCfg.Value + } + _, err := fc.CreateTunnel(rUrl.String, rToken.String, localDomain, targetProto, targetPort, targetAddr) if err != nil { response.WriteJSON(w, response.ErrDefault("Remote tunnel creation failed: "+err.Error())) return diff --git a/go-backend/internal/store/sqlite/repository.go b/go-backend/internal/store/sqlite/repository.go index a94e68b..e4a49b3 100644 --- a/go-backend/internal/store/sqlite/repository.go +++ b/go-backend/internal/store/sqlite/repository.go @@ -121,6 +121,7 @@ type PeerShare struct { IsActive int `json:"isActive"` CreatedTime int64 `json:"createdTime"` UpdatedTime int64 `json:"updatedTime"` + AllowedDomains string `json:"allowedDomains"` } func Open(path string) (*Repository, error) { @@ -1238,12 +1239,25 @@ func ensurePeerSchema(db *sql.DB) error { current_flow INTEGER DEFAULT 0, is_active INTEGER DEFAULT 1, created_time INTEGER NOT NULL, - updated_time INTEGER NOT NULL + updated_time INTEGER NOT NULL, + allowed_domains TEXT DEFAULT '' )`) if err != nil { return fmt.Errorf("create peer_share: %w", err) } + // Add allowed_domains column if it doesn't exist (for existing tables) + var dummy interface{} + err = db.QueryRow("SELECT allowed_domains FROM peer_share LIMIT 1").Scan(&dummy) + if err != nil { + if strings.Contains(err.Error(), "no such column") { + _, err = db.Exec("ALTER TABLE peer_share ADD COLUMN allowed_domains TEXT DEFAULT ''") + if err != nil { + log.Printf("failed to add column allowed_domains to peer_share: %v", err) + } + } + } + columns := map[string]string{ "is_remote": "INTEGER DEFAULT 0", "remote_url": "TEXT", @@ -1271,9 +1285,9 @@ func (r *Repository) CreatePeerShare(share *PeerShare) error { return errors.New("repository not initialized") } _, err := r.db.Exec(` - INSERT INTO peer_share(name, node_id, token, max_bandwidth, expiry_time, port_range_start, port_range_end, current_flow, is_active, created_time, updated_time) - VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `, share.Name, share.NodeID, share.Token, share.MaxBandwidth, share.ExpiryTime, share.PortRangeStart, share.PortRangeEnd, share.CurrentFlow, share.IsActive, share.CreatedTime, share.UpdatedTime) + INSERT INTO peer_share(name, node_id, token, max_bandwidth, expiry_time, port_range_start, port_range_end, current_flow, is_active, created_time, updated_time, allowed_domains) + VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `, share.Name, share.NodeID, share.Token, share.MaxBandwidth, share.ExpiryTime, share.PortRangeStart, share.PortRangeEnd, share.CurrentFlow, share.IsActive, share.CreatedTime, share.UpdatedTime, share.AllowedDomains) return err } @@ -1282,9 +1296,9 @@ func (r *Repository) UpdatePeerShare(share *PeerShare) error { return errors.New("repository not initialized") } _, err := r.db.Exec(` - UPDATE peer_share SET name=?, max_bandwidth=?, expiry_time=?, port_range_start=?, port_range_end=?, is_active=?, updated_time=? + UPDATE peer_share SET name=?, max_bandwidth=?, expiry_time=?, port_range_start=?, port_range_end=?, is_active=?, updated_time=?, allowed_domains=? WHERE id=? - `, share.Name, share.MaxBandwidth, share.ExpiryTime, share.PortRangeStart, share.PortRangeEnd, share.IsActive, share.UpdatedTime, share.ID) + `, share.Name, share.MaxBandwidth, share.ExpiryTime, share.PortRangeStart, share.PortRangeEnd, share.IsActive, share.UpdatedTime, share.AllowedDomains, share.ID) return err } @@ -1300,9 +1314,9 @@ func (r *Repository) GetPeerShare(id int64) (*PeerShare, error) { if r == nil || r.db == nil { return nil, errors.New("repository not initialized") } - row := r.db.QueryRow(`SELECT id, name, node_id, token, max_bandwidth, expiry_time, port_range_start, port_range_end, current_flow, is_active, created_time, updated_time FROM peer_share WHERE id = ?`, id) + row := r.db.QueryRow(`SELECT id, name, node_id, token, max_bandwidth, expiry_time, port_range_start, port_range_end, current_flow, is_active, created_time, updated_time, allowed_domains FROM peer_share WHERE id = ?`, id) var s PeerShare - if err := row.Scan(&s.ID, &s.Name, &s.NodeID, &s.Token, &s.MaxBandwidth, &s.ExpiryTime, &s.PortRangeStart, &s.PortRangeEnd, &s.CurrentFlow, &s.IsActive, &s.CreatedTime, &s.UpdatedTime); err != nil { + if err := row.Scan(&s.ID, &s.Name, &s.NodeID, &s.Token, &s.MaxBandwidth, &s.ExpiryTime, &s.PortRangeStart, &s.PortRangeEnd, &s.CurrentFlow, &s.IsActive, &s.CreatedTime, &s.UpdatedTime, &s.AllowedDomains); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, nil } @@ -1315,9 +1329,9 @@ func (r *Repository) GetPeerShareByToken(token string) (*PeerShare, error) { if r == nil || r.db == nil { return nil, errors.New("repository not initialized") } - row := r.db.QueryRow(`SELECT id, name, node_id, token, max_bandwidth, expiry_time, port_range_start, port_range_end, current_flow, is_active, created_time, updated_time FROM peer_share WHERE token = ?`, token) + row := r.db.QueryRow(`SELECT id, name, node_id, token, max_bandwidth, expiry_time, port_range_start, port_range_end, current_flow, is_active, created_time, updated_time, allowed_domains FROM peer_share WHERE token = ?`, token) var s PeerShare - if err := row.Scan(&s.ID, &s.Name, &s.NodeID, &s.Token, &s.MaxBandwidth, &s.ExpiryTime, &s.PortRangeStart, &s.PortRangeEnd, &s.CurrentFlow, &s.IsActive, &s.CreatedTime, &s.UpdatedTime); err != nil { + if err := row.Scan(&s.ID, &s.Name, &s.NodeID, &s.Token, &s.MaxBandwidth, &s.ExpiryTime, &s.PortRangeStart, &s.PortRangeEnd, &s.CurrentFlow, &s.IsActive, &s.CreatedTime, &s.UpdatedTime, &s.AllowedDomains); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, nil } @@ -1330,7 +1344,7 @@ func (r *Repository) ListPeerShares() ([]PeerShare, error) { if r == nil || r.db == nil { return nil, errors.New("repository not initialized") } - rows, err := r.db.Query(`SELECT id, name, node_id, token, max_bandwidth, expiry_time, port_range_start, port_range_end, current_flow, is_active, created_time, updated_time FROM peer_share ORDER BY id DESC`) + rows, err := r.db.Query(`SELECT id, name, node_id, token, max_bandwidth, expiry_time, port_range_start, port_range_end, current_flow, is_active, created_time, updated_time, allowed_domains FROM peer_share ORDER BY id DESC`) if err != nil { return nil, err } @@ -1339,7 +1353,7 @@ func (r *Repository) ListPeerShares() ([]PeerShare, error) { var shares []PeerShare for rows.Next() { var s PeerShare - if err := rows.Scan(&s.ID, &s.Name, &s.NodeID, &s.Token, &s.MaxBandwidth, &s.ExpiryTime, &s.PortRangeStart, &s.PortRangeEnd, &s.CurrentFlow, &s.IsActive, &s.CreatedTime, &s.UpdatedTime); err != nil { + if err := rows.Scan(&s.ID, &s.Name, &s.NodeID, &s.Token, &s.MaxBandwidth, &s.ExpiryTime, &s.PortRangeStart, &s.PortRangeEnd, &s.CurrentFlow, &s.IsActive, &s.CreatedTime, &s.UpdatedTime, &s.AllowedDomains); err != nil { return nil, err } shares = append(shares, s) diff --git a/install.sh b/install.sh index 8f0cca9..e09394a 100644 --- a/install.sh +++ b/install.sh @@ -28,7 +28,7 @@ COUNTRY=$(curl -s https://ipinfo.io/country) maybe_proxy_url() { local url="$1" if [ "$COUNTRY" = "CN" ]; then - echo "https://ghfast.top/${url}" + echo "https://gcode.hostcentral.cc/${url}" else echo "$url" fi diff --git a/panel_install.sh b/panel_install.sh index e977ba9..bb77c25 100755 --- a/panel_install.sh +++ b/panel_install.sh @@ -15,7 +15,7 @@ COUNTRY=$(curl -s https://ipinfo.io/country) maybe_proxy_url() { local url="$1" if [ "$COUNTRY" = "CN" ]; then - echo "https://ghfast.top/${url}" + echo "https://gcode.hostcentral.cc/${url}" else echo "$url" fi diff --git a/vite-frontend/src/api/index.ts b/vite-frontend/src/api/index.ts index 59b0ffc..c568221 100644 --- a/vite-frontend/src/api/index.ts +++ b/vite-frontend/src/api/index.ts @@ -197,6 +197,7 @@ export const createPeerShare = (data: { expiryTime?: number; portRangeStart?: number; portRangeEnd?: number; + allowedDomains?: string; }) => Network.post("/federation/share/create", data); export const deletePeerShare = (id: number) => Network.post("/federation/share/delete", { id }); diff --git a/vite-frontend/src/pages/config.tsx b/vite-frontend/src/pages/config.tsx index 482d841..4ee5498 100644 --- a/vite-frontend/src/pages/config.tsx +++ b/vite-frontend/src/pages/config.tsx @@ -56,6 +56,13 @@ const CONFIG_ITEMS: ConfigItem[] = [ "格式“ip:port”,用于对接节点时使用,ip是你安装面板服务器的公网ip,端口是安装脚本内输入的后端端口。不要套CDN,不支持https,通讯数据有加密", type: "input", }, + { + key: "panel_domain", + label: "面板域名", + placeholder: "请输入面板域名", + description: "当前面板的域名,用于与其他面板进行联邦共享时验证身份", + type: "input", + }, { key: "app_name", label: "应用名称", @@ -99,6 +106,7 @@ const getInitialConfigs = (): Record => { "cloudflare_site_key", "cloudflare_secret_key", "ip", + "panel_domain", ]; const initialConfigs: Record = {}; diff --git a/vite-frontend/src/pages/panel-sharing.tsx b/vite-frontend/src/pages/panel-sharing.tsx index e0770aa..c78a9ec 100644 --- a/vite-frontend/src/pages/panel-sharing.tsx +++ b/vite-frontend/src/pages/panel-sharing.tsx @@ -34,6 +34,7 @@ interface PeerShare { portRangeStart: number; portRangeEnd: number; isActive: number; + allowedDomains?: string; } export default function PanelSharingPage() { @@ -54,6 +55,7 @@ export default function PanelSharingPage() { expiryDays: 30, portRangeStart: 10000, portRangeEnd: 20000, + allowedDomains: "", }); const [importForm, setImportForm] = useState({ @@ -108,6 +110,7 @@ export default function PanelSharingPage() { expiryTime: shareForm.expiryDays === 0 ? 0 : expiryTime, portRangeStart: shareForm.portRangeStart, portRangeEnd: shareForm.portRangeEnd, + allowedDomains: shareForm.allowedDomains, }); if (res.code === 0) { toast.success("创建成功"); @@ -202,6 +205,7 @@ export default function PanelSharingPage() {

端口范围: {share.portRangeStart} - {share.portRangeEnd}

+ {share.allowedDomains &&

允许域名: {share.allowedDomains}

}

过期时间: {share.expiryTime === 0 ? "永久" : new Date(share.expiryTime).toLocaleDateString()}

@@ -276,6 +280,13 @@ export default function PanelSharingPage() { value={shareForm.expiryDays.toString()} onChange={(e) => setShareForm({ ...shareForm, expiryDays: parseInt(e.target.value) })} /> + setShareForm({ ...shareForm, allowedDomains: e.target.value })} + />