271 lines
8.5 KiB
Go
271 lines
8.5 KiB
Go
package hvpnnode3
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"net/http"
|
|
"net/url"
|
|
"text/template"
|
|
"time"
|
|
|
|
"github.com/biter777/countries"
|
|
"github.com/felixge/httpsnoop"
|
|
"github.com/google/uuid"
|
|
|
|
"gitea.hbanafa.com/HeshamTB/hvpn-node3/proto"
|
|
"gitea.hbanafa.com/HeshamTB/hvpn-node3/templates"
|
|
)
|
|
|
|
type CtxKey string
|
|
const CtxReqID CtxKey = "request_id"
|
|
const CtxCountryCode CtxKey = "country"
|
|
|
|
func HandleGetNodeInfo(wgLink *WGLink) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
reqID := r.Context().Value(CtxReqID).(uuid.UUID)
|
|
debug("Preparing node info for", reqID)
|
|
dev, err := wgLink.Device(wgLink.Name)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
c := r.Context().Value(CtxCountryCode).(countries.CountryCode)
|
|
json.NewEncoder(w).Encode(
|
|
proto.NodePublicInfo{
|
|
PublicKey: dev.PublicKey.String(),
|
|
UDPPort: dev.ListenPort,
|
|
Country: c.String(),
|
|
CountryCode: c.Alpha2(),
|
|
Endpoint: wgLink.Endpoint,
|
|
Type: dev.Type.String(),
|
|
StartedAt: wgLink.StartedAT,
|
|
Uptime: time.Since(wgLink.StartedAT),
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
func HandleGetPeer(wgLink *WGLink) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
reqID := r.Context().Value(CtxReqID).(uuid.UUID)
|
|
pubkey := r.PathValue("pubkey")
|
|
debug(pubkey, reqID)
|
|
peer, err := wgLink.GetPeer(pubkey)
|
|
if err != nil {
|
|
if errors.Is(err, proto.PeerDoesNotExist){
|
|
w.WriteHeader(http.StatusNotFound)
|
|
json.NewEncoder(w).Encode(
|
|
proto.ErrJSONResponse{Message: "Peer does not exist"},
|
|
)
|
|
return
|
|
}
|
|
slog.Error(err.Error())
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
err = json.NewEncoder(w).Encode(proto.WgPeerToPeer(*peer))
|
|
if err != nil {
|
|
slog.Error(err.Error())
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func HandleGetPeers(wgLink *WGLink) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
reqID := r.Context().Value(CtxReqID).(uuid.UUID)
|
|
debug("GET Peers for", reqID)
|
|
dev, err := wgLink.Device(wgLink.Name)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
slog.Error(err.Error())
|
|
return
|
|
}
|
|
var peers []proto.Peer
|
|
for _, peer := range dev.Peers {
|
|
p := proto.WgPeerToPeer(peer)
|
|
peers = append(peers, p)
|
|
}
|
|
err = json.NewEncoder(w).Encode(peers)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|
|
func HandlePostPeer(wgLink *WGLink) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r* http.Request) {
|
|
reqID := r.Context().Value(CtxReqID).(uuid.UUID)
|
|
debug("POST Peer for", reqID)
|
|
peerRequest := proto.CreatePeerRequest{}
|
|
err := json.NewDecoder(r.Body).Decode(&peerRequest)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
json.NewEncoder(w).Encode(
|
|
&proto.ErrJSONResponse{Message: "Invalid request JSON"},
|
|
)
|
|
return
|
|
}
|
|
|
|
if valid := ValidKey(peerRequest.PublicKey); !valid {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
json.NewEncoder(w).Encode(
|
|
proto.ErrJSONResponse{Message: "Invalid public key"},
|
|
)
|
|
return
|
|
}
|
|
|
|
exists, err := wgLink.Exists(peerRequest.PublicKey)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
json.NewEncoder(w).Encode(
|
|
proto.ErrJSONResponse{Message: "Error while checking peer existance"},
|
|
)
|
|
return
|
|
}
|
|
if exists {
|
|
debugf("Peer %s already exists", reqID, peerRequest.PublicKey)
|
|
w.Header().Add("Location", fmt.Sprintf("/peer/%s", url.QueryEscape(peerRequest.PublicKey)))
|
|
w.WriteHeader(http.StatusConflict)
|
|
return
|
|
}
|
|
|
|
peer, err := wgLink.AddPeer(peerRequest.PublicKey)
|
|
if err != nil {
|
|
slog.Error(err.Error())
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
json.NewEncoder(w).Encode(
|
|
proto.ErrJSONResponse{Message: "Error while adding peer"},
|
|
)
|
|
return
|
|
}
|
|
infof("Allocated IP: %s", reqID, peer.AllowedIPs[0].IP.String())
|
|
infof("Peer %s added", reqID, peerRequest.PublicKey)
|
|
w.Header().Set("Content-Type", CONTENT_JSON)
|
|
w.WriteHeader(http.StatusCreated)
|
|
json.NewEncoder(w).Encode(
|
|
proto.WgPeerToPeer(*peer),
|
|
)
|
|
}
|
|
}
|
|
|
|
func HandleDeletePeer(wg *WGLink) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
reqId := r.Context().Value(CtxReqID).(uuid.UUID)
|
|
pubkey := r.PathValue("pubkey")
|
|
debugf("DELETE Peer %s", reqId, pubkey)
|
|
exists, err := wg.Exists(pubkey)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
slog.Error(err.Error())
|
|
return
|
|
}
|
|
if !exists {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
json.NewEncoder(w).Encode(
|
|
proto.ErrJSONResponse{Message: "Peer does not exist"},
|
|
)
|
|
return
|
|
}
|
|
err = wg.DeletePeer(pubkey)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
json.NewEncoder(w).Encode(
|
|
proto.ErrJSONResponse{Message: "Error while deleting peer"},
|
|
)
|
|
slog.Error(err.Error())
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
}
|
|
|
|
func HandleGetCreatePeer(wg *WGLink) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
reqId := r.Context().Value(CtxReqID).(uuid.UUID)
|
|
info("Create peer", reqId)
|
|
newPeer, err := wg.CreateNewPeer()
|
|
if err != nil {
|
|
slog.Error(err.Error())
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
tmpl, err := template.New("client.ini").Parse(templates.ClientINI)
|
|
if err != nil {
|
|
slog.Error(err.Error())
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Add("Content-Type", CONTENT_PLAIN_TEXT)
|
|
err = tmpl.Execute(w, newPeer)
|
|
if err != nil {
|
|
slog.Error(err.Error())
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func HttpLogHandler2(h http.Handler) http.Handler {
|
|
// https://blog.kowalczyk.info/article/e00e89c3841e4f8c8c769a78b8a90b47/logging-http-requests-in-go.html
|
|
fn := func(w http.ResponseWriter, r* http.Request) {
|
|
reqID, _ := uuid.NewRandom()
|
|
rCtx := context.WithValue(r.Context(), CtxReqID, reqID)
|
|
r = r.WithContext(rCtx)
|
|
slog.Info(fmt.Sprintf("Starting request with ID: %s", reqID.String()))
|
|
m := httpsnoop.CaptureMetrics(h, w, r)
|
|
msg := fmt.Sprintf(
|
|
"[HTTP] %s %s %s %d %s %s", r.RemoteAddr, r.Method,
|
|
r.URL.String(),m.Code, m.Duration, reqID.String())
|
|
slog.Info(msg)
|
|
}
|
|
return http.HandlerFunc(fn)
|
|
}
|
|
|
|
func HttpAuthToken(h http.Handler, token string) http.Handler {
|
|
fn := func(w http.ResponseWriter, r* http.Request) {
|
|
if token != "" {
|
|
if r.Header.Get("Authorization") != token {
|
|
slog.Debug("Invalid api key")
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
return
|
|
}
|
|
}
|
|
h.ServeHTTP(w, r)
|
|
}
|
|
return http.HandlerFunc(fn)
|
|
}
|
|
|
|
func WithCountryCtx(h http.Handler, country countries.CountryCode) http.Handler {
|
|
fn := func(w http.ResponseWriter, r* http.Request) {
|
|
rr := r.WithContext(context.WithValue(r.Context(), CtxCountryCode, country))
|
|
h.ServeHTTP(w, rr)
|
|
}
|
|
return http.HandlerFunc(fn)
|
|
}
|
|
|
|
func debugf(format string, reqID uuid.UUID, args ...any) {
|
|
format = format + " " + reqID.String()
|
|
slog.Debug(fmt.Sprintf(format, args...))
|
|
}
|
|
|
|
func debug(msg string, reqID uuid.UUID) {
|
|
msg = msg + " " + reqID.String()
|
|
debugf("%s", reqID, msg)
|
|
}
|
|
|
|
func infof(format string, reqID uuid.UUID, args ...any) {
|
|
format = format + " " + reqID.String()
|
|
slog.Info(fmt.Sprintf(format, args...))
|
|
}
|
|
|
|
func info(msg string, reqID uuid.UUID) {
|
|
msg = msg + " " + reqID.String()
|
|
infof("%s", reqID, msg)
|
|
}
|