package hvpnnode3 import ( "context" "encoding/json" "errors" "fmt" "log/slog" "net/http" "net/url" "text/template" "time" "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" 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 } json.NewEncoder(w).Encode( proto.NodePublicInfo{ PublicKey: dev.PublicKey.String(), UDPPort: dev.ListenPort, 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){ json.NewEncoder(w).Encode( proto.ErrJSONResponse{Message: "Peer does not exist"}, ) w.WriteHeader(http.StatusNotFound) return } 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 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) }