package http import ( "encoding/json" "net/http" "unicode/utf8" "github.com/gorilla/mux" "github.com/netbirdio/netbird/management/server" "github.com/netbirdio/netbird/management/server/http/api" "github.com/netbirdio/netbird/management/server/http/util" "github.com/netbirdio/netbird/management/server/jwtclaims" "github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/route" ) // RoutesHandler is the routes handler of the account type RoutesHandler struct { accountManager server.AccountManager claimsExtractor *jwtclaims.ClaimsExtractor } // NewRoutesHandler returns a new instance of RoutesHandler handler func NewRoutesHandler(accountManager server.AccountManager, authCfg AuthCfg) *RoutesHandler { return &RoutesHandler{ accountManager: accountManager, claimsExtractor: jwtclaims.NewClaimsExtractor( jwtclaims.WithAudience(authCfg.Audience), jwtclaims.WithUserIDClaim(authCfg.UserIDClaim), ), } } // GetAllRoutes returns the list of routes for the account func (h *RoutesHandler) GetAllRoutes(w http.ResponseWriter, r *http.Request) { claims := h.claimsExtractor.FromRequestContext(r) account, user, err := h.accountManager.GetAccountFromToken(claims) if err != nil { util.WriteError(err, w) return } routes, err := h.accountManager.ListRoutes(account.Id, user.Id) if err != nil { util.WriteError(err, w) return } apiRoutes := make([]*api.Route, 0) for _, r := range routes { apiRoutes = append(apiRoutes, toRouteResponse(r)) } util.WriteJSONObject(w, apiRoutes) } // CreateRoute handles route creation request func (h *RoutesHandler) CreateRoute(w http.ResponseWriter, r *http.Request) { claims := h.claimsExtractor.FromRequestContext(r) account, user, err := h.accountManager.GetAccountFromToken(claims) if err != nil { util.WriteError(err, w) return } var req api.PostApiRoutesJSONRequestBody err = json.NewDecoder(r.Body).Decode(&req) if err != nil { util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w) return } _, newPrefix, err := route.ParseNetwork(req.Network) if err != nil { util.WriteError(err, w) return } if utf8.RuneCountInString(req.NetworkId) > route.MaxNetIDChar || req.NetworkId == "" { util.WriteError(status.Errorf(status.InvalidArgument, "identifier should be between 1 and %d", route.MaxNetIDChar), w) return } peerId := "" if req.Peer != nil { peerId = *req.Peer } peerGroupIds := []string{} if req.PeerGroups != nil { peerGroupIds = *req.PeerGroups } if (peerId != "" && len(peerGroupIds) > 0) || (peerId == "" && len(peerGroupIds) == 0) { util.WriteError(status.Errorf(status.InvalidArgument, "only one peer or peer_groups should be provided"), w) return } // do not allow non Linux peers if peer := account.GetPeer(peerId); peer != nil { if peer.Meta.GoOS != "linux" { util.WriteError(status.Errorf(status.InvalidArgument, "non-linux peers are non supported as network routes"), w) return } } newRoute, err := h.accountManager.CreateRoute( account.Id, newPrefix.String(), peerId, peerGroupIds, req.Description, req.NetworkId, req.Masquerade, req.Metric, req.Groups, req.Enabled, user.Id, ) if err != nil { util.WriteError(err, w) return } resp := toRouteResponse(newRoute) util.WriteJSONObject(w, &resp) } // UpdateRoute handles update to a route identified by a given ID func (h *RoutesHandler) UpdateRoute(w http.ResponseWriter, r *http.Request) { claims := h.claimsExtractor.FromRequestContext(r) account, user, err := h.accountManager.GetAccountFromToken(claims) if err != nil { util.WriteError(err, w) return } vars := mux.Vars(r) routeID := vars["routeId"] if len(routeID) == 0 { util.WriteError(status.Errorf(status.InvalidArgument, "invalid route ID"), w) return } _, err = h.accountManager.GetRoute(account.Id, routeID, user.Id) if err != nil { util.WriteError(err, w) return } var req api.PutApiRoutesRouteIdJSONRequestBody err = json.NewDecoder(r.Body).Decode(&req) if err != nil { util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w) return } prefixType, newPrefix, err := route.ParseNetwork(req.Network) if err != nil { util.WriteError(status.Errorf(status.InvalidArgument, "couldn't parse update prefix %s for route ID %s", req.Network, routeID), w) return } if utf8.RuneCountInString(req.NetworkId) > route.MaxNetIDChar || req.NetworkId == "" { util.WriteError(status.Errorf(status.InvalidArgument, "identifier should be between 1 and %d", route.MaxNetIDChar), w) return } if req.Peer != nil && req.PeerGroups != nil { util.WriteError(status.Errorf(status.InvalidArgument, "only peer or peers_group should be provided"), w) return } if req.Peer == nil && req.PeerGroups == nil { util.WriteError(status.Errorf(status.InvalidArgument, "either peer or peers_group should be provided"), w) return } peerID := "" if req.Peer != nil { peerID = *req.Peer } // do not allow non Linux peers if peer := account.GetPeer(peerID); peer != nil { if peer.Meta.GoOS != "linux" { util.WriteError(status.Errorf(status.InvalidArgument, "non-linux peers are non supported as network routes"), w) return } } newRoute := &route.Route{ ID: routeID, Network: newPrefix, NetID: req.NetworkId, NetworkType: prefixType, Masquerade: req.Masquerade, Metric: req.Metric, Description: req.Description, Enabled: req.Enabled, Groups: req.Groups, } if req.Peer != nil { newRoute.Peer = peerID } if req.PeerGroups != nil { newRoute.PeerGroups = *req.PeerGroups } err = h.accountManager.SaveRoute(account.Id, user.Id, newRoute) if err != nil { util.WriteError(err, w) return } resp := toRouteResponse(newRoute) util.WriteJSONObject(w, &resp) } // DeleteRoute handles route deletion request func (h *RoutesHandler) DeleteRoute(w http.ResponseWriter, r *http.Request) { claims := h.claimsExtractor.FromRequestContext(r) account, user, err := h.accountManager.GetAccountFromToken(claims) if err != nil { util.WriteError(err, w) return } routeID := mux.Vars(r)["routeId"] if len(routeID) == 0 { util.WriteError(status.Errorf(status.InvalidArgument, "invalid route ID"), w) return } err = h.accountManager.DeleteRoute(account.Id, routeID, user.Id) if err != nil { util.WriteError(err, w) return } util.WriteJSONObject(w, emptyObject{}) } // GetRoute handles a route Get request identified by ID func (h *RoutesHandler) GetRoute(w http.ResponseWriter, r *http.Request) { claims := h.claimsExtractor.FromRequestContext(r) account, user, err := h.accountManager.GetAccountFromToken(claims) if err != nil { util.WriteError(err, w) return } routeID := mux.Vars(r)["routeId"] if len(routeID) == 0 { util.WriteError(status.Errorf(status.InvalidArgument, "invalid route ID"), w) return } foundRoute, err := h.accountManager.GetRoute(account.Id, routeID, user.Id) if err != nil { util.WriteError(status.Errorf(status.NotFound, "route not found"), w) return } util.WriteJSONObject(w, toRouteResponse(foundRoute)) } func toRouteResponse(serverRoute *route.Route) *api.Route { route := &api.Route{ Id: serverRoute.ID, Description: serverRoute.Description, NetworkId: serverRoute.NetID, Enabled: serverRoute.Enabled, Peer: &serverRoute.Peer, Network: serverRoute.Network.String(), NetworkType: serverRoute.NetworkType.String(), Masquerade: serverRoute.Masquerade, Metric: serverRoute.Metric, Groups: serverRoute.Groups, } if len(serverRoute.PeerGroups) > 0 { route.PeerGroups = &serverRoute.PeerGroups } return route }