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
}