Files
panel-vpn/internal/handlers/handlers.go
T

196 lines
5.6 KiB
Go

package handlers
import (
"database/sql"
"html/template"
"log"
"net/http"
"path/filepath"
"github.com/google/uuid"
"github.com/gorilla/mux"
"github.com/orohi/vpn-panel/internal/auth"
"github.com/orohi/vpn-panel/internal/db"
"github.com/orohi/vpn-panel/internal/secretbox"
)
type Server struct {
DB *sql.DB
Auth *auth.Manager
Templates *template.Template
SecretKey []byte
}
type pageData map[string]any
func New(database *sql.DB, authMgr *auth.Manager, appSecret, templatesDir string) (*Server, error) {
pattern := filepath.Join(templatesDir, "*.html")
tmpl, err := template.New("").Funcs(template.FuncMap{
"statusClass": func(s string) string {
switch s {
case "online":
return "on"
case "installing":
return "warn"
case "error":
return "err"
case "offline":
return "off"
default:
return "off"
}
},
}).ParseGlob(pattern)
if err != nil {
return nil, err
}
return &Server{
DB: database,
Auth: authMgr,
Templates: tmpl,
SecretKey: secretbox.DeriveKey(appSecret),
}, nil
}
func (s *Server) Routes() http.Handler {
r := mux.NewRouter()
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("web/static"))))
r.HandleFunc("/", s.home).Methods(http.MethodGet)
r.HandleFunc("/login", s.loginGet).Methods(http.MethodGet)
r.HandleFunc("/login", s.loginPost).Methods(http.MethodPost)
r.HandleFunc("/logout", s.logout).Methods(http.MethodPost, http.MethodGet)
r.HandleFunc("/admin", s.Auth.RequireAuth(s.dashboard)).Methods(http.MethodGet)
r.HandleFunc("/admin/protocols", s.Auth.RequireAuth(s.protocols)).Methods(http.MethodGet)
r.HandleFunc("/admin/protocols/{id}/toggle", s.Auth.RequireAuth(s.toggleProtocol)).Methods(http.MethodPost)
r.HandleFunc("/admin/nodes", s.Auth.RequireAuth(s.nodesList)).Methods(http.MethodGet)
r.HandleFunc("/admin/nodes/new", s.Auth.RequireAuth(s.nodesNewGet)).Methods(http.MethodGet)
r.HandleFunc("/admin/nodes/new", s.Auth.RequireAuth(s.nodesCreate)).Methods(http.MethodPost)
r.HandleFunc("/admin/nodes/{id}", s.Auth.RequireAuth(s.nodeView)).Methods(http.MethodGet)
r.HandleFunc("/admin/nodes/{id}/install", s.Auth.RequireAuth(s.nodeInstall)).Methods(http.MethodPost)
r.HandleFunc("/admin/nodes/{id}/check", s.Auth.RequireAuth(s.nodeCheck)).Methods(http.MethodPost)
r.HandleFunc("/admin/nodes/{id}/delete", s.Auth.RequireAuth(s.nodeDelete)).Methods(http.MethodPost)
r.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
}).Methods(http.MethodGet)
return r
}
func (s *Server) render(w http.ResponseWriter, name string, data pageData) {
if data == nil {
data = pageData{}
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := s.Templates.ExecuteTemplate(w, name, data); err != nil {
log.Printf("template %s: %v", name, err)
http.Error(w, "template error", http.StatusInternalServerError)
}
}
func (s *Server) home(w http.ResponseWriter, r *http.Request) {
if s.Auth.IsAuthenticated(r) {
http.Redirect(w, r, "/admin", http.StatusSeeOther)
return
}
s.render(w, "home.html", pageData{
"Title": "VPN Panel",
})
}
func (s *Server) loginGet(w http.ResponseWriter, r *http.Request) {
if s.Auth.IsAuthenticated(r) {
http.Redirect(w, r, "/admin", http.StatusSeeOther)
return
}
s.render(w, "login.html", pageData{
"Title": "Вход",
"Error": "",
})
}
func (s *Server) loginPost(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
email := r.FormValue("email")
password := r.FormValue("password")
user, err := db.GetUserByEmail(s.DB, email)
if err != nil || user == nil || !auth.CheckPassword(user.PasswordHash, password) {
s.render(w, "login.html", pageData{
"Title": "Вход",
"Error": "Неверный email или пароль",
"Email": email,
})
return
}
if err := s.Auth.Login(w, r, user.ID.String(), user.Email, user.Name); err != nil {
http.Error(w, "session error", http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/admin", http.StatusSeeOther)
}
func (s *Server) logout(w http.ResponseWriter, r *http.Request) {
_ = s.Auth.Logout(w, r)
http.Redirect(w, r, "/login", http.StatusSeeOther)
}
func (s *Server) dashboard(w http.ResponseWriter, r *http.Request) {
stats, err := db.GetStats(s.DB)
if err != nil {
http.Error(w, "db error", http.StatusInternalServerError)
return
}
protocols, err := db.ListProtocols(s.DB)
if err != nil {
http.Error(w, "db error", http.StatusInternalServerError)
return
}
nodes, _ := db.ListNodes(s.DB)
s.render(w, "dashboard.html", pageData{
"Title": "Админка",
"UserName": s.Auth.CurrentName(r),
"Stats": stats,
"Protocols": protocols,
"Nodes": nodes,
"Active": "dashboard",
})
}
func (s *Server) protocols(w http.ResponseWriter, r *http.Request) {
protocols, err := db.ListProtocols(s.DB)
if err != nil {
http.Error(w, "db error", http.StatusInternalServerError)
return
}
s.render(w, "protocols.html", pageData{
"Title": "Протоколы",
"UserName": s.Auth.CurrentName(r),
"Protocols": protocols,
"Active": "protocols",
})
}
func (s *Server) toggleProtocol(w http.ResponseWriter, r *http.Request) {
idStr := mux.Vars(r)["id"]
id, err := uuid.Parse(idStr)
if err != nil {
http.Error(w, "invalid id", http.StatusBadRequest)
return
}
if err := db.ToggleProtocol(s.DB, id); err != nil {
http.Error(w, "db error", http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/admin/protocols", http.StatusSeeOther)
}