From 85438d1dce3b866213f9456ad607a4f3b97b60c4 Mon Sep 17 00:00:00 2001 From: Christoph Haas Date: Fri, 7 Jul 2023 15:36:07 +0200 Subject: [PATCH] creation of multiple peers --- .../src/components/PeerMultiCreateModal.vue | 97 +++ frontend/src/stores/peers.js | 13 + frontend/src/views/InterfaceView.vue | 5 +- internal/adapters/database.go | 2 + .../app/api/core/assets/doc/v0_swagger.json | 596 +++++++++++++++++- .../app/api/core/assets/doc/v0_swagger.yaml | 392 +++++++++++- ...ew-332dbb61.js => ProfileView-dcaf2465.js} | 2 +- ...rView-6cdfb1f6.js => UserView-c9f71a71.js} | 2 +- .../{index-8f53e6dd.js => index-b5bbe402.js} | 62 +- .../app/api/core/frontend-dist/index.html | 2 +- .../app/api/v0/handlers/endpoint_config.go | 11 +- .../app/api/v0/handlers/endpoint_peers.go | 40 ++ internal/app/api/v0/model/models_peer.go | 12 + internal/app/repos.go | 1 + internal/app/wireguard/wireguard.go | 27 + internal/domain/peer.go | 5 + 16 files changed, 1182 insertions(+), 87 deletions(-) create mode 100644 frontend/src/components/PeerMultiCreateModal.vue rename internal/app/api/core/frontend-dist/assets/{ProfileView-332dbb61.js => ProfileView-dcaf2465.js} (98%) rename internal/app/api/core/frontend-dist/assets/{UserView-6cdfb1f6.js => UserView-c9f71a71.js} (99%) rename internal/app/api/core/frontend-dist/assets/{index-8f53e6dd.js => index-b5bbe402.js} (86%) diff --git a/frontend/src/components/PeerMultiCreateModal.vue b/frontend/src/components/PeerMultiCreateModal.vue new file mode 100644 index 0000000..c1d4e91 --- /dev/null +++ b/frontend/src/components/PeerMultiCreateModal.vue @@ -0,0 +1,97 @@ + + + diff --git a/frontend/src/stores/peers.js b/frontend/src/stores/peers.js index 8ede9c4..6edb661 100644 --- a/frontend/src/stores/peers.js +++ b/frontend/src/stores/peers.js @@ -167,6 +167,19 @@ export const peerStore = defineStore({ throw new Error(error) }) }, + async CreateMultiplePeers(interfaceId, formData) { + this.fetching = true + return apiWrapper.post(`${baseUrl}/iface/${base64_url_encode(interfaceId)}/multiplenew`, formData) + .then(peers => { + this.peers.push(...peers) + this.fetching = false + }) + .catch(error => { + this.fetching = false + console.log(error) + throw new Error(error) + }) + }, async LoadPeers(interfaceId) { // if no interfaceId is given, use the currently selected interface if (!interfaceId) { diff --git a/frontend/src/views/InterfaceView.vue b/frontend/src/views/InterfaceView.vue index 9074c03..4d632b3 100644 --- a/frontend/src/views/InterfaceView.vue +++ b/frontend/src/views/InterfaceView.vue @@ -1,6 +1,7 @@ - + diff --git a/internal/app/api/v0/handlers/endpoint_config.go b/internal/app/api/v0/handlers/endpoint_config.go index 109b465..baeba63 100644 --- a/internal/app/api/v0/handlers/endpoint_config.go +++ b/internal/app/api/v0/handlers/endpoint_config.go @@ -7,7 +7,9 @@ import ( "github.com/gin-gonic/gin" "github.com/h44z/wg-portal/internal/app" "html/template" + "net" "net/http" + "net/url" ) //go:embed frontend_config.js.gotpl @@ -52,7 +54,14 @@ func (e configEndpoint) handleConfigJsGet() gin.HandlerFunc { return func(c *gin.Context) { backendUrl := fmt.Sprintf("%s/api/v0", e.app.Config.Web.ExternalUrl) if c.GetHeader("x-wg-dev") != "" { - backendUrl = "http://localhost:5000/api/v0" // override if reqest comes from frontend started with npm run dev + referer := c.Request.Header.Get("Referer") + host := "localhost" + port := "5000" + parsedReferer, err := url.Parse(referer) + if err == nil { + host, port, _ = net.SplitHostPort(parsedReferer.Host) + } + backendUrl = fmt.Sprintf("http://%s:%s/api/v0", host, port) // override if request comes from frontend started with npm run dev } buf := &bytes.Buffer{} err := e.tpl.ExecuteTemplate(buf, "frontend_config.js.gotpl", gin.H{ diff --git a/internal/app/api/v0/handlers/endpoint_peers.go b/internal/app/api/v0/handlers/endpoint_peers.go index 46ea1f9..dadcbb5 100644 --- a/internal/app/api/v0/handlers/endpoint_peers.go +++ b/internal/app/api/v0/handlers/endpoint_peers.go @@ -24,6 +24,7 @@ func (e peerEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *authenti apiGroup.GET("/iface/:iface/all", e.handleAllGet()) apiGroup.GET("/iface/:iface/prepare", e.handlePrepareGet()) apiGroup.POST("/iface/:iface/new", e.handleCreatePost()) + apiGroup.POST("/iface/:iface/multiplenew", e.handleCreateMultiplePost()) apiGroup.GET("/config-qr/:id", e.handleQrCodeGet()) apiGroup.GET("/config/:id", e.handleConfigGet()) apiGroup.GET("/:id", e.handleSingleGet()) @@ -168,6 +169,45 @@ func (e peerEndpoint) handleCreatePost() gin.HandlerFunc { } } +// handleCreateMultiplePost returns a gorm handler function. +// +// @ID peers_handleCreateMultiplePost +// @Tags Peer +// @Summary Create multiple new peers for the given interface. +// @Produce json +// @Param iface path string true "The interface identifier" +// @Param request body model.MultiPeerRequest true "The peer creation request data" +// @Success 200 {object} []model.Peer +// @Failure 400 {object} model.Error +// @Failure 500 {object} model.Error +// @Router /peer/iface/{iface}/multiplenew [post] +func (e peerEndpoint) handleCreateMultiplePost() gin.HandlerFunc { + return func(c *gin.Context) { + ctx := domain.SetUserInfoFromGin(c) + + interfaceId := Base64UrlDecode(c.Param("iface")) + if interfaceId == "" { + c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing iface parameter"}) + return + } + + var req model.MultiPeerRequest + err := c.BindJSON(&req) + if err != nil { + c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: err.Error()}) + return + } + + newPeers, err := e.app.CreateMultiplePeers(ctx, domain.InterfaceIdentifier(interfaceId), model.NewDomainPeerCreationRequest(&req)) + if err != nil { + c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()}) + return + } + + c.JSON(http.StatusOK, model.NewPeers(newPeers)) + } +} + // handleUpdatePut returns a gorm handler function. // // @ID peers_handleUpdatePut diff --git a/internal/app/api/v0/model/models_peer.go b/internal/app/api/v0/model/models_peer.go index aa9f46f..3189624 100644 --- a/internal/app/api/v0/model/models_peer.go +++ b/internal/app/api/v0/model/models_peer.go @@ -131,3 +131,15 @@ func NewDomainPeer(src *Peer) *domain.Peer { return res } + +type MultiPeerRequest struct { + Identifiers []string `json:"Identifiers"` + Suffix string `json:"Suffix"` +} + +func NewDomainPeerCreationRequest(src *MultiPeerRequest) *domain.PeerCreationRequest { + return &domain.PeerCreationRequest{ + Identifiers: src.Identifiers, + Suffix: src.Suffix, + } +} diff --git a/internal/app/repos.go b/internal/app/repos.go index 72a6801..ba72152 100644 --- a/internal/app/repos.go +++ b/internal/app/repos.go @@ -40,6 +40,7 @@ type WireGuardManager interface { PreparePeer(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Peer, error) GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error) CreatePeer(ctx context.Context, p *domain.Peer) (*domain.Peer, error) + CreateMultiplePeers(ctx context.Context, id domain.InterfaceIdentifier, r *domain.PeerCreationRequest) ([]domain.Peer, error) UpdatePeer(ctx context.Context, p *domain.Peer) (*domain.Peer, error) DeletePeer(ctx context.Context, id domain.PeerIdentifier) error } diff --git a/internal/app/wireguard/wireguard.go b/internal/app/wireguard/wireguard.go index 9ce46fe..00f204c 100644 --- a/internal/app/wireguard/wireguard.go +++ b/internal/app/wireguard/wireguard.go @@ -775,6 +775,33 @@ func (m Manager) CreatePeer(ctx context.Context, peer *domain.Peer) (*domain.Pee return peer, nil } +func (m Manager) CreateMultiplePeers(ctx context.Context, interfaceId domain.InterfaceIdentifier, r *domain.PeerCreationRequest) ([]domain.Peer, error) { + var newPeers []domain.Peer + + for _, id := range r.Identifiers { + freshPeer, err := m.PreparePeer(ctx, interfaceId) + if err != nil { + return nil, fmt.Errorf("failed to prepare peer for interface %s: %w", interfaceId, err) + } + + freshPeer.UserIdentifier = domain.UserIdentifier(id) // use id as user identifier. peers are allowed to have invalid user identifiers + if r.Suffix != "" { + freshPeer.DisplayName += " " + r.Suffix + } + + newPeers = append(newPeers, *freshPeer) + } + + for i, peer := range newPeers { + _, err := m.CreatePeer(ctx, &newPeers[i]) + if err != nil { + return nil, fmt.Errorf("failed to create peer %s (uid: %s) for interface %s: %w", peer.Identifier, peer.UserIdentifier, interfaceId, err) + } + } + + return newPeers, nil +} + func (m Manager) UpdatePeer(ctx context.Context, peer *domain.Peer) (*domain.Peer, error) { existingPeer, err := m.db.GetPeer(ctx, peer.Identifier) if err != nil { diff --git a/internal/domain/peer.go b/internal/domain/peer.go index f5b3ffd..6063394 100644 --- a/internal/domain/peer.go +++ b/internal/domain/peer.go @@ -181,3 +181,8 @@ func MergeToPhysicalPeer(pp *PhysicalPeer, p *Peer) { pp.PublicKey = p.Interface.PublicKey pp.PersistentKeepalive = p.PersistentKeepalive.GetValue() } + +type PeerCreationRequest struct { + Identifiers []string + Suffix string +}