diff --git a/frontend/src/helpers/models.js b/frontend/src/helpers/models.js index 6e1e52b..5c0d1d7 100644 --- a/frontend/src/helpers/models.js +++ b/frontend/src/helpers/models.js @@ -53,6 +53,7 @@ export function freshPeer() { Identifier: "", DisplayName: "", UserIdentifier: "", + UserDisplayName: "", InterfaceIdentifier: "", Disabled: false, ExpiresAt: null, diff --git a/frontend/src/views/InterfaceView.vue b/frontend/src/views/InterfaceView.vue index b28cf5f..ecd253e 100644 --- a/frontend/src/views/InterfaceView.vue +++ b/frontend/src/views/InterfaceView.vue @@ -400,7 +400,7 @@ onMounted(async () => { {{peer.DisplayName}}{{ $filters.truncate(peer.Identifier, 10)}} - {{peer.UserIdentifier}} + {{peer.UserIdentifier}} {{ ip }} diff --git a/internal/app/api/v0/model/models_peer.go b/internal/app/api/v0/model/models_peer.go index b0e2f7e..c7843c5 100644 --- a/internal/app/api/v0/model/models_peer.go +++ b/internal/app/api/v0/model/models_peer.go @@ -43,6 +43,7 @@ type Peer struct { Identifier string `json:"Identifier" example:"super_nice_peer"` // peer unique identifier DisplayName string `json:"DisplayName"` // a nice display name/ description for the peer UserIdentifier string `json:"UserIdentifier"` // the owner + UserDisplayName string `json:"UserDisplayName"` // the owner display name InterfaceIdentifier string `json:"InterfaceIdentifier"` // the interface id Disabled bool `json:"Disabled"` // flag that specifies if the peer is enabled (up) or not (down) DisabledReason string `json:"DisabledReason"` // the reason why the peer has been disabled @@ -80,7 +81,7 @@ type Peer struct { } func NewPeer(src *domain.Peer) *Peer { - return &Peer{ + p := &Peer{ Identifier: string(src.Identifier), DisplayName: src.DisplayName, UserIdentifier: string(src.UserIdentifier), @@ -111,6 +112,12 @@ func NewPeer(src *domain.Peer) *Peer { PostDown: ConfigOptionFromDomain(src.Interface.PostDown), Filename: src.GetConfigFileName(), } + + if src.User != nil { + p.UserDisplayName = src.User.DisplayName() + } + + return p } func NewPeers(src []domain.Peer) []Peer { diff --git a/internal/domain/peer.go b/internal/domain/peer.go index c47f63e..2ea3254 100644 --- a/internal/domain/peer.go +++ b/internal/domain/peer.go @@ -1,12 +1,14 @@ package domain import ( + "errors" "fmt" "net" "strings" "time" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + "gorm.io/gorm" "github.com/h44z/wg-portal/internal" "github.com/h44z/wg-portal/internal/config" @@ -44,6 +46,7 @@ type Peer struct { DisplayName string // a nice display name/ description for the peer Identifier PeerIdentifier `gorm:"primaryKey;column:identifier"` // peer unique identifier UserIdentifier UserIdentifier `gorm:"index;column:user_identifier"` // the owner + User *User `gorm:"-"` // the owner user object; loaded automatically after fetch InterfaceIdentifier InterfaceIdentifier `gorm:"index;column:interface_identifier"` // the interface id Disabled *time.Time `gorm:"column:disabled"` // if this field is set, the peer is disabled DisabledReason string // the reason why the peer has been disabled @@ -348,3 +351,26 @@ type PeerCreationRequest struct { UserIdentifiers []string Prefix string } + +// AfterFind is a GORM hook that automatically loads the associated User object +// based on the UserIdentifier field. If the identifier is empty or no user is +// found, the User field is set to nil. +func (p *Peer) AfterFind(tx *gorm.DB) error { + if p == nil { + return nil + } + if p.UserIdentifier == "" { + p.User = nil + return nil + } + var u User + if err := tx.Where("identifier = ?", p.UserIdentifier).First(&u).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + p.User = nil + return nil + } + return err + } + p.User = &u + return nil +} diff --git a/internal/domain/user.go b/internal/domain/user.go index 84b5345..ee5f371 100644 --- a/internal/domain/user.go +++ b/internal/domain/user.go @@ -185,6 +185,27 @@ func (u *User) CopyCalculatedAttributes(src *User) { u.LinkedPeerCount = src.LinkedPeerCount } +// DisplayName returns the display name of the user. +// The display name is the first and last name, or the email address of the user. +// If none of these fields are set, the user identifier is returned. +func (u *User) DisplayName() string { + var displayName string + switch { + case u.Firstname != "" && u.Lastname != "": + displayName = fmt.Sprintf("%s %s", u.Firstname, u.Lastname) + case u.Firstname != "": + displayName = u.Firstname + case u.Lastname != "": + displayName = u.Lastname + case u.Email != "": + displayName = u.Email + default: + displayName = string(u.Identifier) + } + + return displayName +} + // region webauthn func (u *User) WebAuthnID() []byte { @@ -209,19 +230,7 @@ func (u *User) WebAuthnName() string { } func (u *User) WebAuthnDisplayName() string { - var userName string - switch { - case u.Firstname != "" && u.Lastname != "": - userName = fmt.Sprintf("%s %s", u.Firstname, u.Lastname) - case u.Firstname != "": - userName = u.Firstname - case u.Lastname != "": - userName = u.Lastname - default: - userName = string(u.Identifier) - } - - return userName + return u.DisplayName() } func (u *User) WebAuthnCredentials() []webauthn.Credential {