hvpn-node3/handlers.go

271 lines
8.5 KiB
Go
Raw Permalink Normal View History

package hvpnnode3
import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"net/http"
"net/url"
"text/template"
2024-03-24 16:13:26 +01:00
"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,
2024-03-18 00:06:33 +01:00
Type: dev.Type.String(),
2024-03-24 16:13:26 +01:00
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)
}