Implement new GET endpoint `/frpcAct/proxyMgr/list` to retrieve proxy configurations from frpc instance config files. Includes handler function, API documentation, and route setup. The endpoint validates user permissions, reads and parses the config file, and returns structured proxy data with instance information.
355 lines
12 KiB
Go
355 lines
12 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"super-frpc/postLog"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
)
|
|
|
|
type CreateProxyRequest struct {
|
|
InstanceID string `json:"instanceID"`
|
|
ProxyInfo FrpcProxyInfo `json:"proxyInfo"`
|
|
}
|
|
|
|
func CreateProxyHandler(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method")
|
|
postLog.Debug(fmt.Sprintf("[CreateProxyHandler] Invalid request method: %s", r.Method))
|
|
return
|
|
}
|
|
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[CreateProxyHandler] Failed to read request body: %v", err))
|
|
SendErrorResponse(w, http.StatusBadRequest, "Failed to read request body")
|
|
return
|
|
}
|
|
defer r.Body.Close()
|
|
|
|
var reqMap map[string]interface{}
|
|
if err := json.Unmarshal(body, &reqMap); err != nil {
|
|
postLog.Error(fmt.Sprintf("[CreateProxyHandler] Failed to unmarshal request body: %v", err))
|
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid request format")
|
|
return
|
|
}
|
|
|
|
instanceID := getStringFromMap(reqMap, "instanceID")
|
|
if instanceID == "" {
|
|
postLog.Error("[CreateProxyHandler] instanceID is required")
|
|
SendErrorResponse(w, http.StatusBadRequest, "instanceID is required")
|
|
return
|
|
}
|
|
|
|
proxyInfoMap, ok := reqMap["proxyInfo"].(map[string]interface{})
|
|
if !ok {
|
|
postLog.Error("[CreateProxyHandler] Invalid proxyInfo format")
|
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid proxyInfo format")
|
|
return
|
|
}
|
|
|
|
proxyInfo := FrpcProxyInfo{
|
|
Name: getStringFromMap(proxyInfoMap, "name"),
|
|
Type: getStringFromMap(proxyInfoMap, "type"),
|
|
LocalIP: getStringFromMap(proxyInfoMap, "localIP"),
|
|
LocalPort: getStringFromMap(proxyInfoMap, "localPort"),
|
|
RemotePort: getStringFromMap(proxyInfoMap, "remotePort"),
|
|
}
|
|
|
|
if proxyInfo.Name == "" || proxyInfo.Type == "" || proxyInfo.LocalIP == "" ||
|
|
proxyInfo.LocalPort == "" || proxyInfo.RemotePort == "" {
|
|
postLog.Error("[CreateProxyHandler] Missing required fields in proxyInfo")
|
|
SendErrorResponse(w, http.StatusBadRequest, "Missing required fields in proxyInfo")
|
|
return
|
|
}
|
|
|
|
userID, _, err := ValidateRequestWithHeader(w, r)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[CreateProxyHandler] Failed to validate request header: %v", err))
|
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid request header")
|
|
return
|
|
}
|
|
|
|
userType, err := GetUserType(userID)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[CreateProxyHandler] Failed to get user type: %v", err))
|
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user type")
|
|
return
|
|
} else if userType != "admin" && userType != "superuser" {
|
|
SendErrorResponse(w, http.StatusForbidden, "Permission Denied")
|
|
return
|
|
}
|
|
|
|
var instance FrpcInstance
|
|
instanceIDInt, _ := strconv.Atoi(instanceID)
|
|
instance, err = DBQueryFrpcInstanceByID(instanceIDInt)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[CreateProxyHandler] Failed to query instance: %v", err))
|
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance")
|
|
return
|
|
}
|
|
|
|
if instance.UserID != userID {
|
|
postLog.Error(fmt.Sprintf("[CreateProxyHandler] Instance not found for user %d", userID))
|
|
SendErrorResponse(w, http.StatusNotFound, "Instance not found")
|
|
return
|
|
}
|
|
|
|
configContent, err := os.ReadFile(instance.ConfigPath)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[CreateProxyHandler] Failed to read config file %s: %v", instance.ConfigPath, err))
|
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to read config file")
|
|
return
|
|
}
|
|
|
|
updatedContent, err := addFrpcProxy(string(configContent), proxyInfo)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[CreateProxyHandler] Failed to add proxy: %v", err))
|
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to add proxy")
|
|
return
|
|
}
|
|
|
|
if err := os.WriteFile(instance.ConfigPath, []byte(updatedContent), 0644); err != nil {
|
|
postLog.Error(fmt.Sprintf("[CreateProxyHandler] Failed to write config file %s: %v", instance.ConfigPath, err))
|
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to write config file")
|
|
return
|
|
}
|
|
|
|
SendSuccessResponse(w, "Proxy created successfully", map[string]interface{}{
|
|
"instanceName": instance.Name,
|
|
"instanceID": instance.ID,
|
|
"configPath": instance.ConfigPath,
|
|
"proxyName": proxyInfo.Name,
|
|
})
|
|
postLog.Info(fmt.Sprintf("[CreateProxyHandler] Proxy %s created successfully for instance %s", proxyInfo.Name, instance.Name))
|
|
}
|
|
|
|
func addFrpcProxy(configContent string, info FrpcProxyInfo) (string, error) {
|
|
var config FrpcConfig
|
|
if _, err := toml.Decode(configContent, &config); err != nil {
|
|
return "", fmt.Errorf("failed to parse config: %w", err)
|
|
}
|
|
|
|
proxy := map[string]interface{}{
|
|
"name": info.Name,
|
|
"type": info.Type,
|
|
"local_ip": info.LocalIP,
|
|
"local_port": info.LocalPort,
|
|
"remote_port": info.RemotePort,
|
|
}
|
|
|
|
config.Proxies = append(config.Proxies, proxy)
|
|
|
|
var buf strings.Builder
|
|
if err := toml.NewEncoder(&buf).Encode(config); err != nil {
|
|
return "", fmt.Errorf("failed to write config: %w", err)
|
|
}
|
|
|
|
return buf.String(), nil
|
|
}
|
|
|
|
func DeleteProxyHandler(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method")
|
|
postLog.Debug(fmt.Sprintf("[DeleteProxyHandler] Invalid request method: %s", r.Method))
|
|
return
|
|
}
|
|
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[DeleteProxyHandler] Failed to read request body: %v", err))
|
|
SendErrorResponse(w, http.StatusBadRequest, "Failed to read request body")
|
|
return
|
|
}
|
|
defer r.Body.Close()
|
|
|
|
var reqMap map[string]interface{}
|
|
if err := json.Unmarshal(body, &reqMap); err != nil {
|
|
postLog.Error(fmt.Sprintf("[DeleteProxyHandler] Failed to unmarshal request body: %v", err))
|
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid request format")
|
|
return
|
|
}
|
|
|
|
instanceID := getStringFromMap(reqMap, "instanceID")
|
|
if instanceID == "" {
|
|
postLog.Error("[DeleteProxyHandler] instanceID is required")
|
|
SendErrorResponse(w, http.StatusBadRequest, "instanceID is required")
|
|
return
|
|
}
|
|
|
|
proxyName := getStringFromMap(reqMap, "proxyName")
|
|
if proxyName == "" {
|
|
postLog.Error("[DeleteProxyHandler] proxyName is required")
|
|
SendErrorResponse(w, http.StatusBadRequest, "proxyName is required")
|
|
return
|
|
}
|
|
|
|
userID, _, err := ValidateRequestWithHeader(w, r)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[DeleteProxyHandler] Failed to validate request header: %v", err))
|
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid request header")
|
|
return
|
|
}
|
|
|
|
userType, err := GetUserType(userID)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[DeleteProxyHandler] Failed to get user type: %v", err))
|
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user type")
|
|
return
|
|
} else if userType != "admin" && userType != "superuser" {
|
|
SendErrorResponse(w, http.StatusForbidden, "Permission Denied")
|
|
return
|
|
}
|
|
|
|
var instance FrpcInstance
|
|
instanceIDInt, _ := strconv.Atoi(instanceID)
|
|
instance, err = DBQueryFrpcInstanceByID(instanceIDInt)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[DeleteProxyHandler] Failed to query instance: %v", err))
|
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance")
|
|
return
|
|
}
|
|
|
|
if instance.UserID != userID {
|
|
postLog.Error(fmt.Sprintf("[DeleteProxyHandler] Instance not found for user %d", userID))
|
|
SendErrorResponse(w, http.StatusNotFound, "Instance not found")
|
|
return
|
|
}
|
|
|
|
configContent, err := os.ReadFile(instance.ConfigPath)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[DeleteProxyHandler] Failed to read config file %s: %v", instance.ConfigPath, err))
|
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to read config file")
|
|
return
|
|
}
|
|
|
|
updatedContent, err := removeFrpcProxy(string(configContent), proxyName)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[DeleteProxyHandler] Failed to remove proxy: %v", err))
|
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to remove proxy")
|
|
return
|
|
}
|
|
|
|
if err := os.WriteFile(instance.ConfigPath, []byte(updatedContent), 0644); err != nil {
|
|
postLog.Error(fmt.Sprintf("[DeleteProxyHandler] Failed to write config file %s: %v", instance.ConfigPath, err))
|
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to write config file")
|
|
return
|
|
}
|
|
|
|
SendSuccessResponse(w, "Proxy deleted successfully", map[string]interface{}{
|
|
"instanceName": instance.Name,
|
|
"instanceID": instance.ID,
|
|
"configPath": instance.ConfigPath,
|
|
"proxyName": proxyName,
|
|
})
|
|
postLog.Info(fmt.Sprintf("[DeleteProxyHandler] Proxy %s deleted successfully from instance %s", proxyName, instance.Name))
|
|
}
|
|
|
|
func removeFrpcProxy(configContent string, proxyName string) (string, error) {
|
|
var config FrpcConfig
|
|
if _, err := toml.Decode(configContent, &config); err != nil {
|
|
return "", fmt.Errorf("failed to parse config: %w", err)
|
|
}
|
|
|
|
var found bool
|
|
var newProxies []map[string]interface{}
|
|
for _, proxy := range config.Proxies {
|
|
if name, ok := proxy["name"].(string); ok && name == proxyName {
|
|
found = true
|
|
continue
|
|
}
|
|
newProxies = append(newProxies, proxy)
|
|
}
|
|
|
|
if !found {
|
|
return "", fmt.Errorf("proxy %s not found", proxyName)
|
|
}
|
|
|
|
config.Proxies = newProxies
|
|
|
|
var buf strings.Builder
|
|
if err := toml.NewEncoder(&buf).Encode(config); err != nil {
|
|
return "", fmt.Errorf("failed to write config: %w", err)
|
|
}
|
|
|
|
return buf.String(), nil
|
|
}
|
|
|
|
func ListProxiesHandler(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method")
|
|
postLog.Debug(fmt.Sprintf("[ListProxiesHandler] Invalid request method: %s", r.Method))
|
|
return
|
|
}
|
|
|
|
queryParams := r.URL.Query()
|
|
instanceID := queryParams.Get("instanceID")
|
|
if instanceID == "" {
|
|
postLog.Error("[ListProxiesHandler] instanceID is required")
|
|
SendErrorResponse(w, http.StatusBadRequest, "instanceID is required")
|
|
return
|
|
}
|
|
|
|
userID, _, err := ValidateRequestWithHeader(w, r)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[ListProxiesHandler] Failed to validate request header: %v", err))
|
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid request header")
|
|
return
|
|
}
|
|
|
|
var instance FrpcInstance
|
|
instanceIDInt, _ := strconv.Atoi(instanceID)
|
|
instance, err = DBQueryFrpcInstanceByID(instanceIDInt)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[ListProxiesHandler] Failed to query instance: %v", err))
|
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance")
|
|
return
|
|
}
|
|
|
|
if instance.UserID != userID {
|
|
postLog.Error(fmt.Sprintf("[ListProxiesHandler] Instance not found for user %d", userID))
|
|
SendErrorResponse(w, http.StatusNotFound, "Instance not found")
|
|
return
|
|
}
|
|
|
|
configContent, err := os.ReadFile(instance.ConfigPath)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[ListProxiesHandler] Failed to read config file %s: %v", instance.ConfigPath, err))
|
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to read config file")
|
|
return
|
|
}
|
|
|
|
var config FrpcConfig
|
|
if _, err := toml.Decode(string(configContent), &config); err != nil {
|
|
postLog.Error(fmt.Sprintf("[ListProxiesHandler] Failed to parse config: %v", err))
|
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to parse config file")
|
|
return
|
|
}
|
|
|
|
proxyList := make([]map[string]interface{}, len(config.Proxies))
|
|
for i, proxy := range config.Proxies {
|
|
proxyData := map[string]interface{}{
|
|
"name": proxy["name"],
|
|
"type": proxy["type"],
|
|
"localIP": proxy["localIP"],
|
|
"localPort": proxy["localPort"],
|
|
"remotePort": proxy["remotePort"],
|
|
}
|
|
proxyList[i] = proxyData
|
|
}
|
|
|
|
SendSuccessResponse(w, "Proxies listed successfully", map[string]interface{}{
|
|
"instanceID": instance.ID,
|
|
"instanceName": instance.Name,
|
|
"proxyCount": len(proxyList),
|
|
"proxies": proxyList,
|
|
})
|
|
postLog.Info(fmt.Sprintf("[ListProxiesHandler] Retrieved %d proxies for instance %s", len(proxyList), instance.Name))
|
|
}
|