549 lines
14 KiB
Go
549 lines
14 KiB
Go
package server
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"html/template"
|
||
"net/http"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"wg-panel/internal/database"
|
||
"wg-panel/internal/wireguard"
|
||
)
|
||
|
||
var (
|
||
DB *database.Database
|
||
Config *database.Config
|
||
)
|
||
|
||
// authMiddleware проверяет авторизацию
|
||
func authMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
||
return func(w http.ResponseWriter, r *http.Request) {
|
||
// Проверяем cookie
|
||
cookie, err := r.Cookie("auth")
|
||
if err != nil || cookie.Value != "authenticated" {
|
||
if r.URL.Path == "/" {
|
||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||
return
|
||
}
|
||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||
return
|
||
}
|
||
next(w, r)
|
||
}
|
||
}
|
||
|
||
// handleLogin обрабатывает страницу входа
|
||
func HandleLogin(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method == "GET" {
|
||
tmplData, err := TemplatesFS.ReadFile("templates/login.html")
|
||
if err != nil {
|
||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||
return
|
||
}
|
||
tmpl, err := template.New("login").Parse(string(tmplData))
|
||
if err != nil {
|
||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||
return
|
||
}
|
||
tmpl.Execute(w, nil)
|
||
return
|
||
}
|
||
|
||
if r.Method == "POST" {
|
||
username := r.FormValue("username")
|
||
password := r.FormValue("password")
|
||
|
||
if username == Config.Username && password == Config.Password {
|
||
http.SetCookie(w, &http.Cookie{
|
||
Name: "auth",
|
||
Value: "authenticated",
|
||
Path: "/",
|
||
MaxAge: 86400, // 24 часа
|
||
HttpOnly: true,
|
||
})
|
||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||
return
|
||
}
|
||
|
||
http.Redirect(w, r, "/login?error=1", http.StatusSeeOther)
|
||
}
|
||
}
|
||
|
||
// handleLogout обрабатывает выход
|
||
func HandleLogout(w http.ResponseWriter, r *http.Request) {
|
||
http.SetCookie(w, &http.Cookie{
|
||
Name: "auth",
|
||
Value: "",
|
||
Path: "/",
|
||
MaxAge: -1,
|
||
HttpOnly: true,
|
||
})
|
||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||
}
|
||
|
||
// handleIndex обрабатывает главную страницу
|
||
func HandleIndex(w http.ResponseWriter, r *http.Request) {
|
||
tmplData, err := TemplatesFS.ReadFile("templates/index.html")
|
||
if err != nil {
|
||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||
return
|
||
}
|
||
tmpl, err := template.New("index").Parse(string(tmplData))
|
||
if err != nil {
|
||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||
return
|
||
}
|
||
tmpl.Execute(w, nil)
|
||
}
|
||
|
||
// === ОБРАБОТЧИКИ СЕРВЕРОВ ===
|
||
|
||
// HandleServers возвращает список серверов
|
||
func HandleServers(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json")
|
||
if DB.Servers == nil {
|
||
json.NewEncoder(w).Encode([]database.Server{})
|
||
} else {
|
||
json.NewEncoder(w).Encode(DB.Servers)
|
||
}
|
||
}
|
||
|
||
// HandleCreateServer создает новый WireGuard сервер
|
||
func HandleCreateServer(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method != "POST" {
|
||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||
return
|
||
}
|
||
|
||
name := r.FormValue("name")
|
||
address := r.FormValue("address")
|
||
portStr := r.FormValue("port")
|
||
dns := r.FormValue("dns")
|
||
|
||
if name == "" || address == "" || portStr == "" {
|
||
http.Error(w, "Missing required fields", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
// Добавляем /24 если не указано
|
||
if !strings.Contains(address, "/") {
|
||
address = address + "/24"
|
||
}
|
||
|
||
port, err := strconv.Atoi(portStr)
|
||
if err != nil {
|
||
http.Error(w, "Invalid port", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
// Валидируем конфигурацию
|
||
if err := database.ValidateServerConfig(DB, address, port); err != nil {
|
||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
server, err := wireguard.CreateServer(DB, name, address, port, dns)
|
||
if err != nil {
|
||
http.Error(w, "Failed to create server: "+err.Error(), http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
DB.Servers = append(DB.Servers, *server)
|
||
database.SaveDatabase(DB)
|
||
|
||
w.Header().Set("Content-Type", "application/json")
|
||
json.NewEncoder(w).Encode(server)
|
||
}
|
||
|
||
// HandleUpdateServer обновляет настройки сервера
|
||
func HandleUpdateServer(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method != "POST" {
|
||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||
return
|
||
}
|
||
|
||
id := r.FormValue("id")
|
||
name := r.FormValue("name")
|
||
portStr := r.FormValue("port")
|
||
dns := r.FormValue("dns")
|
||
|
||
for i, server := range DB.Servers {
|
||
if server.ID == id {
|
||
if name != "" {
|
||
DB.Servers[i].Name = name
|
||
}
|
||
if portStr != "" {
|
||
port, err := strconv.Atoi(portStr)
|
||
if err == nil {
|
||
DB.Servers[i].ListenPort = port
|
||
}
|
||
}
|
||
if dns != "" {
|
||
DB.Servers[i].DNS = dns
|
||
}
|
||
|
||
// Обновляем конфиг файл
|
||
wireguard.UpdateServerConfig(&DB.Servers[i], DB)
|
||
|
||
database.SaveDatabase(DB)
|
||
w.Header().Set("Content-Type", "application/json")
|
||
json.NewEncoder(w).Encode(DB.Servers[i])
|
||
return
|
||
}
|
||
}
|
||
|
||
http.Error(w, "Server not found", http.StatusNotFound)
|
||
}
|
||
|
||
// HandleDeleteServer удаляет сервер
|
||
func HandleDeleteServer(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method != "POST" {
|
||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||
return
|
||
}
|
||
|
||
id := r.FormValue("id")
|
||
|
||
for i, server := range DB.Servers {
|
||
if server.ID == id {
|
||
// Удаляем сервер
|
||
if err := wireguard.DeleteServer(&server); err != nil {
|
||
http.Error(w, "Failed to delete server", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
// Удаляем всех клиентов этого сервера
|
||
var newClients []database.Client
|
||
for _, client := range DB.Clients {
|
||
if client.ServerID != id {
|
||
newClients = append(newClients, client)
|
||
}
|
||
}
|
||
DB.Clients = newClients
|
||
|
||
// Удаляем сервер из базы
|
||
DB.Servers = append(DB.Servers[:i], DB.Servers[i+1:]...)
|
||
database.SaveDatabase(DB)
|
||
|
||
w.WriteHeader(http.StatusOK)
|
||
return
|
||
}
|
||
}
|
||
|
||
http.Error(w, "Server not found", http.StatusNotFound)
|
||
}
|
||
|
||
// HandleToggleServer включает/выключает сервер
|
||
func HandleToggleServer(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method != "POST" {
|
||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||
return
|
||
}
|
||
|
||
id := r.FormValue("id")
|
||
|
||
for i, server := range DB.Servers {
|
||
if server.ID == id {
|
||
if err := wireguard.ToggleServer(&DB.Servers[i]); err != nil {
|
||
http.Error(w, "Failed to toggle server: "+err.Error(), http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
database.SaveDatabase(DB)
|
||
w.Header().Set("Content-Type", "application/json")
|
||
json.NewEncoder(w).Encode(DB.Servers[i])
|
||
return
|
||
}
|
||
}
|
||
|
||
http.Error(w, "Server not found", http.StatusNotFound)
|
||
}
|
||
|
||
// === ОБРАБОТЧИКИ КЛИЕНТОВ ===
|
||
|
||
// HandleClients возвращает список клиентов
|
||
func HandleClients(w http.ResponseWriter, r *http.Request) {
|
||
serverID := r.URL.Query().Get("server_id")
|
||
|
||
var clients []database.Client
|
||
for _, client := range DB.Clients {
|
||
if serverID == "" || client.ServerID == serverID {
|
||
clients = append(clients, client)
|
||
}
|
||
}
|
||
|
||
w.Header().Set("Content-Type", "application/json")
|
||
if clients == nil {
|
||
json.NewEncoder(w).Encode([]database.Client{})
|
||
} else {
|
||
json.NewEncoder(w).Encode(clients)
|
||
}
|
||
}
|
||
|
||
// HandleCreateClient создает новый конфиг клиента
|
||
func HandleCreateClient(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method != "POST" {
|
||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||
return
|
||
}
|
||
|
||
serverID := r.FormValue("server_id")
|
||
name := r.FormValue("name")
|
||
comment := r.FormValue("comment")
|
||
|
||
if serverID == "" || name == "" {
|
||
http.Error(w, "Missing required fields", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
client, err := wireguard.CreateClient(DB, serverID, name, comment)
|
||
if err != nil {
|
||
http.Error(w, "Failed to create client: "+err.Error(), http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
DB.Clients = append(DB.Clients, *client)
|
||
database.SaveDatabase(DB)
|
||
|
||
w.Header().Set("Content-Type", "application/json")
|
||
json.NewEncoder(w).Encode(client)
|
||
}
|
||
|
||
// HandleDeleteClient удаляет клиента
|
||
func HandleDeleteClient(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method != "POST" {
|
||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||
return
|
||
}
|
||
|
||
id := r.FormValue("id")
|
||
|
||
for i, client := range DB.Clients {
|
||
if client.ID == id {
|
||
// Удаляем клиента
|
||
if err := wireguard.DeleteClient(DB, &client); err != nil {
|
||
http.Error(w, "Failed to delete client", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
// Удаляем из базы
|
||
DB.Clients = append(DB.Clients[:i], DB.Clients[i+1:]...)
|
||
database.SaveDatabase(DB)
|
||
|
||
w.WriteHeader(http.StatusOK)
|
||
return
|
||
}
|
||
}
|
||
|
||
http.Error(w, "Client not found", http.StatusNotFound)
|
||
}
|
||
|
||
// HandleToggleClient включает/выключает клиента
|
||
func HandleToggleClient(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method != "POST" {
|
||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||
return
|
||
}
|
||
|
||
id := r.FormValue("id")
|
||
|
||
for i, client := range DB.Clients {
|
||
if client.ID == id {
|
||
if err := wireguard.ToggleClient(DB, &DB.Clients[i]); err != nil {
|
||
http.Error(w, "Failed to toggle client", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
database.SaveDatabase(DB)
|
||
w.Header().Set("Content-Type", "application/json")
|
||
json.NewEncoder(w).Encode(DB.Clients[i])
|
||
return
|
||
}
|
||
}
|
||
|
||
http.Error(w, "Client not found", http.StatusNotFound)
|
||
}
|
||
|
||
// HandleUpdateClient обновляет имя и комментарий клиента
|
||
func HandleUpdateClient(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method != "POST" {
|
||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||
return
|
||
}
|
||
|
||
id := r.FormValue("id")
|
||
name := r.FormValue("name")
|
||
comment := r.FormValue("comment")
|
||
|
||
for i, client := range DB.Clients {
|
||
if client.ID == id {
|
||
if name != "" {
|
||
DB.Clients[i].Name = name
|
||
}
|
||
DB.Clients[i].Comment = comment
|
||
database.SaveDatabase(DB)
|
||
w.Header().Set("Content-Type", "application/json")
|
||
json.NewEncoder(w).Encode(DB.Clients[i])
|
||
return
|
||
}
|
||
}
|
||
|
||
http.Error(w, "Client not found", http.StatusNotFound)
|
||
}
|
||
|
||
// HandleAddPortForward добавляет проброс порта
|
||
func HandleAddPortForward(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method != "POST" {
|
||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||
return
|
||
}
|
||
|
||
clientID := r.FormValue("client_id")
|
||
portStr := r.FormValue("port")
|
||
protocol := r.FormValue("protocol")
|
||
description := r.FormValue("description")
|
||
|
||
port, err := strconv.Atoi(portStr)
|
||
if err != nil {
|
||
http.Error(w, "Invalid port", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
if protocol != "tcp" && protocol != "udp" && protocol != "both" {
|
||
http.Error(w, "Protocol must be tcp, udp or both", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
for i, client := range DB.Clients {
|
||
if client.ID == clientID {
|
||
if err := wireguard.AddPortForward(DB, &DB.Clients[i], port, protocol, description); err != nil {
|
||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
database.SaveDatabase(DB)
|
||
w.Header().Set("Content-Type", "application/json")
|
||
json.NewEncoder(w).Encode(DB.Clients[i])
|
||
return
|
||
}
|
||
}
|
||
|
||
http.Error(w, "Client not found", http.StatusNotFound)
|
||
}
|
||
|
||
// HandleRemovePortForward удаляет проброс порта
|
||
func HandleRemovePortForward(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method != "POST" {
|
||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||
return
|
||
}
|
||
|
||
clientID := r.FormValue("client_id")
|
||
portStr := r.FormValue("port")
|
||
protocol := r.FormValue("protocol")
|
||
|
||
port, err := strconv.Atoi(portStr)
|
||
if err != nil {
|
||
http.Error(w, "Invalid port", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
for i, client := range DB.Clients {
|
||
if client.ID == clientID {
|
||
if err := wireguard.RemovePortForward(&DB.Clients[i], port, protocol); err != nil {
|
||
http.Error(w, err.Error(), http.StatusNotFound)
|
||
return
|
||
}
|
||
|
||
database.SaveDatabase(DB)
|
||
w.Header().Set("Content-Type", "application/json")
|
||
json.NewEncoder(w).Encode(DB.Clients[i])
|
||
return
|
||
}
|
||
}
|
||
|
||
http.Error(w, "Client not found", http.StatusNotFound)
|
||
}
|
||
|
||
// HandleDownloadConfig скачивает конфиг клиента
|
||
func HandleDownloadConfig(w http.ResponseWriter, r *http.Request) {
|
||
id := r.URL.Query().Get("id")
|
||
|
||
for _, client := range DB.Clients {
|
||
if client.ID == id {
|
||
// Находим сервер
|
||
var server *database.Server
|
||
for i := range DB.Servers {
|
||
if DB.Servers[i].ID == client.ServerID {
|
||
server = &DB.Servers[i]
|
||
break
|
||
}
|
||
}
|
||
|
||
if server == nil {
|
||
http.Error(w, "Server not found", http.StatusNotFound)
|
||
return
|
||
}
|
||
|
||
config := wireguard.GenerateClientConfig(client, server)
|
||
|
||
// Создаем безопасное имя файла (без пробелов и спецсимволов)
|
||
safeName := database.SanitizeFilename(client.Name)
|
||
|
||
w.Header().Set("Content-Type", "application/x-wireguard-profile")
|
||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.conf", safeName))
|
||
w.Write([]byte(config))
|
||
return
|
||
}
|
||
}
|
||
|
||
http.Error(w, "Client not found", http.StatusNotFound)
|
||
}
|
||
|
||
// HandleQRCode генерирует QR код для конфига
|
||
func HandleQRCode(w http.ResponseWriter, r *http.Request) {
|
||
id := r.URL.Query().Get("id")
|
||
|
||
for _, client := range DB.Clients {
|
||
if client.ID == id {
|
||
// Находим сервер
|
||
var server *database.Server
|
||
for i := range DB.Servers {
|
||
if DB.Servers[i].ID == client.ServerID {
|
||
server = &DB.Servers[i]
|
||
break
|
||
}
|
||
}
|
||
|
||
if server == nil {
|
||
http.Error(w, "Server not found", http.StatusNotFound)
|
||
return
|
||
}
|
||
|
||
config := wireguard.GenerateClientConfig(client, server)
|
||
|
||
// Генерируем QR код
|
||
png, err := wireguard.GenerateQRCode(config)
|
||
if err != nil {
|
||
http.Error(w, "Failed to generate QR code", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
w.Header().Set("Content-Type", "image/png")
|
||
w.Write(png)
|
||
return
|
||
}
|
||
}
|
||
|
||
http.Error(w, "Client not found", http.StatusNotFound)
|
||
}
|
||
|
||
// HandleStats возвращает статистику
|
||
func HandleStats(w http.ResponseWriter, r *http.Request) {
|
||
wireguard.UpdateStats(DB)
|
||
w.Header().Set("Content-Type", "application/json")
|
||
json.NewEncoder(w).Encode(DB.Clients)
|
||
}
|