diff --git a/frontend/src/components/PeerMultiCreateModal.vue b/frontend/src/components/PeerMultiCreateModal.vue index 5201f03..f5a2c87 100644 --- a/frontend/src/components/PeerMultiCreateModal.vue +++ b/frontend/src/components/PeerMultiCreateModal.vue @@ -32,7 +32,7 @@ const selectedInterface = computed(() => { function freshForm() { return { Identifiers: [], - Suffix: "", + Prefix: "", } } @@ -102,7 +102,7 @@ async function save() {
- + {{ $t('modals.peer-multi-create.prefix.description') }}
diff --git a/internal/app/api/core/assets/doc/v0_swagger.json b/internal/app/api/core/assets/doc/v0_swagger.json index fc2ebc8..d4c39d1 100644 --- a/internal/app/api/core/assets/doc/v0_swagger.json +++ b/internal/app/api/core/assets/doc/v0_swagger.json @@ -1969,7 +1969,7 @@ "type": "string" } }, - "Suffix": { + "Prefix": { "type": "string" } } diff --git a/internal/app/api/core/assets/doc/v0_swagger.yaml b/internal/app/api/core/assets/doc/v0_swagger.yaml index 479cb8d..bb0de5f 100644 --- a/internal/app/api/core/assets/doc/v0_swagger.yaml +++ b/internal/app/api/core/assets/doc/v0_swagger.yaml @@ -206,7 +206,7 @@ definitions: items: type: string type: array - Suffix: + Prefix: type: string type: object model.Peer: diff --git a/internal/app/api/core/server.go b/internal/app/api/core/server.go index 87b5519..4cc986c 100644 --- a/internal/app/api/core/server.go +++ b/internal/app/api/core/server.go @@ -139,7 +139,8 @@ func (s *Server) setupRoutes(endpoints ...ApiEndpointSetupFunc) { s.versions[version].HandleFunc("GET /swagger/index.html", s.rapiDocHandler(version)) // Deprecated: old link s.versions[version].HandleFunc("GET /doc.html", s.rapiDocHandler(version)) - groupSetupFn(s.versions[version]) + versionGroup := s.versions[version].Group() + groupSetupFn(versionGroup) } } } diff --git a/internal/app/api/v0/model/models_peer.go b/internal/app/api/v0/model/models_peer.go index 4f8f2b3..b0e2f7e 100644 --- a/internal/app/api/v0/model/models_peer.go +++ b/internal/app/api/v0/model/models_peer.go @@ -172,13 +172,13 @@ func NewDomainPeer(src *Peer) *domain.Peer { type MultiPeerRequest struct { Identifiers []string `json:"Identifiers"` - Suffix string `json:"Suffix"` + Prefix string `json:"Prefix"` } func NewDomainPeerCreationRequest(src *MultiPeerRequest) *domain.PeerCreationRequest { return &domain.PeerCreationRequest{ UserIdentifiers: src.Identifiers, - Suffix: src.Suffix, + Prefix: src.Prefix, } } diff --git a/internal/app/wireguard/wireguard_peers.go b/internal/app/wireguard/wireguard_peers.go index 1406e85..500d5bb 100644 --- a/internal/app/wireguard/wireguard_peers.go +++ b/internal/app/wireguard/wireguard_peers.go @@ -188,29 +188,29 @@ func (m Manager) CreatePeer(ctx context.Context, peer *domain.Peer) (*domain.Pee sessionUser := domain.GetUserInfo(ctx) - // Enforce peer limit for non-admin users if LimitAdditionalUserPeers is set - if m.cfg.Core.SelfProvisioningAllowed && !sessionUser.IsAdmin && m.cfg.Advanced.LimitAdditionalUserPeers > 0 { - peers, err := m.db.GetUserPeers(ctx, peer.UserIdentifier) - if err != nil { - return nil, fmt.Errorf("failed to fetch peers for user %s: %w", peer.UserIdentifier, err) - } - // Count enabled peers (disabled IS NULL) - peerCount := 0 - for _, p := range peers { - if !p.IsDisabled() { - peerCount++ - } - } - totalAllowedPeers := 1 + m.cfg.Advanced.LimitAdditionalUserPeers // 1 default peer + x additional peers - if peerCount >= totalAllowedPeers { - slog.WarnContext(ctx, "peer creation blocked due to limit", - "user", peer.UserIdentifier, - "current_count", peerCount, - "allowed_count", totalAllowedPeers) - return nil, fmt.Errorf("peer limit reached (%d peers allowed): %w", totalAllowedPeers, domain.ErrNoPermission) - } - } - + // Enforce peer limit for non-admin users if LimitAdditionalUserPeers is set + if m.cfg.Core.SelfProvisioningAllowed && !sessionUser.IsAdmin && m.cfg.Advanced.LimitAdditionalUserPeers > 0 { + peers, err := m.db.GetUserPeers(ctx, peer.UserIdentifier) + if err != nil { + return nil, fmt.Errorf("failed to fetch peers for user %s: %w", peer.UserIdentifier, err) + } + // Count enabled peers (disabled IS NULL) + peerCount := 0 + for _, p := range peers { + if !p.IsDisabled() { + peerCount++ + } + } + totalAllowedPeers := 1 + m.cfg.Advanced.LimitAdditionalUserPeers // 1 default peer + x additional peers + if peerCount >= totalAllowedPeers { + slog.WarnContext(ctx, "peer creation blocked due to limit", + "user", peer.UserIdentifier, + "current_count", peerCount, + "allowed_count", totalAllowedPeers) + return nil, fmt.Errorf("peer limit reached (%d peers allowed): %w", totalAllowedPeers, + domain.ErrNoPermission) + } + } existingPeer, err := m.db.GetPeer(ctx, peer.Identifier) if err != nil && !errors.Is(err, domain.ErrNotFound) { @@ -257,7 +257,7 @@ func (m Manager) CreateMultiplePeers( return nil, err } - var newPeers []*domain.Peer + createdPeers := make([]domain.Peer, 0, len(r.UserIdentifiers)) for _, id := range r.UserIdentifiers { freshPeer, err := m.PreparePeer(ctx, interfaceId) @@ -266,27 +266,22 @@ func (m Manager) CreateMultiplePeers( } 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 + if r.Prefix != "" { + freshPeer.DisplayName = r.Prefix + " " + freshPeer.DisplayName } if err := m.validatePeerCreation(ctx, nil, freshPeer); err != nil { return nil, fmt.Errorf("creation not allowed: %w", err) } - newPeers = append(newPeers, freshPeer) - } + // Save immediately to reserve the assigned IPs so the next prepared peer gets the next free IPs + if err := m.savePeers(ctx, freshPeer); err != nil { + return nil, fmt.Errorf("failed to create new peer %s: %w", freshPeer.Identifier, err) + } - err := m.savePeers(ctx, newPeers...) - if err != nil { - return nil, fmt.Errorf("failed to create new peers: %w", err) - } + createdPeers = append(createdPeers, *freshPeer) - createdPeers := make([]domain.Peer, len(newPeers)) - for i := range newPeers { - createdPeers[i] = *newPeers[i] - - m.bus.Publish(app.TopicPeerCreated, *newPeers[i]) + m.bus.Publish(app.TopicPeerCreated, *freshPeer) } return createdPeers, nil diff --git a/internal/domain/peer.go b/internal/domain/peer.go index 8de712a..93404eb 100644 --- a/internal/domain/peer.go +++ b/internal/domain/peer.go @@ -269,5 +269,5 @@ func MergeToPhysicalPeer(pp *PhysicalPeer, p *Peer) { type PeerCreationRequest struct { UserIdentifiers []string - Suffix string + Prefix string }