mirror of
https://github.com/h44z/wg-portal.git
synced 2025-09-14 06:51:15 +00:00
API - CRUD for peers, interfaces and users (#340)
Public REST API implementation to handle peers, interfaces and users. It also includes some simple provisioning endpoints. The Swagger API documentation is available under /api/v1/doc.html
This commit is contained in:
46
internal/app/api/v1/models/model_options.go
Normal file
46
internal/app/api/v1/models/model_options.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/h44z/wg-portal/internal"
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
)
|
||||
|
||||
type ConfigOption[T any] struct {
|
||||
Value T `json:"Value"`
|
||||
Overridable bool `json:"Overridable,omitempty"`
|
||||
}
|
||||
|
||||
func NewConfigOption[T any](value T, overridable bool) ConfigOption[T] {
|
||||
return ConfigOption[T]{
|
||||
Value: value,
|
||||
Overridable: overridable,
|
||||
}
|
||||
}
|
||||
|
||||
func ConfigOptionFromDomain[T any](opt domain.ConfigOption[T]) ConfigOption[T] {
|
||||
return ConfigOption[T]{
|
||||
Value: opt.Value,
|
||||
Overridable: opt.Overridable,
|
||||
}
|
||||
}
|
||||
|
||||
func ConfigOptionToDomain[T any](opt ConfigOption[T]) domain.ConfigOption[T] {
|
||||
return domain.ConfigOption[T]{
|
||||
Value: opt.Value,
|
||||
Overridable: opt.Overridable,
|
||||
}
|
||||
}
|
||||
|
||||
func StringSliceConfigOptionFromDomain(opt domain.ConfigOption[string]) ConfigOption[[]string] {
|
||||
return ConfigOption[[]string]{
|
||||
Value: internal.SliceString(opt.Value),
|
||||
Overridable: opt.Overridable,
|
||||
}
|
||||
}
|
||||
|
||||
func StringSliceConfigOptionToDomain(opt ConfigOption[[]string]) domain.ConfigOption[string] {
|
||||
return domain.ConfigOption[string]{
|
||||
Value: internal.SliceToString(opt.Value),
|
||||
Overridable: opt.Overridable,
|
||||
}
|
||||
}
|
8
internal/app/api/v1/models/models.go
Normal file
8
internal/app/api/v1/models/models.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package models
|
||||
|
||||
// Error represents an error response.
|
||||
type Error struct {
|
||||
Code int `json:"Code"` // HTTP status code.
|
||||
Message string `json:"Message"` // Error message.
|
||||
Details string `json:"Details,omitempty"` // Additional error details.
|
||||
}
|
201
internal/app/api/v1/models/models_interface.go
Normal file
201
internal/app/api/v1/models/models_interface.go
Normal file
@@ -0,0 +1,201 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/h44z/wg-portal/internal"
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
)
|
||||
|
||||
// Interface represents a WireGuard interface.
|
||||
type Interface struct {
|
||||
// Identifier is the unique identifier of the interface. It is always equal to the device name of the interface.
|
||||
Identifier string `json:"Identifier" example:"wg0" binding:"required"`
|
||||
// DisplayName is a nice display name / description for the interface.
|
||||
DisplayName string `json:"DisplayName" binding:"omitempty,max=64" example:"My Interface"`
|
||||
// Mode is the interface type, either 'server', 'client' or 'any'. The mode specifies how WireGuard Portal handles peers for this interface.
|
||||
Mode string `json:"Mode" example:"server" binding:"required,oneof=server client any"`
|
||||
// PrivateKey is the private key of the interface.
|
||||
PrivateKey string `json:"PrivateKey" example:"gI6EdUSYvn8ugXOt8QQD6Yc+JyiZxIhp3GInSWRfWGE=" binding:"required,len=44"`
|
||||
// PublicKey is the public key of the server interface. The public key is used by peers to connect to the server.
|
||||
PublicKey string `json:"PublicKey" example:"HIgo9xNzJMWLKASShiTqIybxZ0U3wGLiUeJ1PKf8ykw=" binding:"required,len=44"`
|
||||
// Disabled is a flag that specifies if the interface is enabled (up) or not (down). Disabled interfaces are not able to accept connections.
|
||||
Disabled bool `json:"Disabled" example:"false"`
|
||||
// DisabledReason is the reason why the interface has been disabled.
|
||||
DisabledReason string `json:"DisabledReason" binding:"required_if=Disabled true" example:"This is a reason why the interface has been disabled."`
|
||||
// SaveConfig is a flag that specifies if the configuration should be saved to the configuration file (wgX.conf in wg-quick format).
|
||||
SaveConfig bool `json:"SaveConfig" example:"false"`
|
||||
|
||||
// ListenPort is the listening port, for example: 51820. The listening port is only required for server interfaces.
|
||||
ListenPort int `json:"ListenPort" binding:"omitempty,min=1,max=65535" example:"51820"`
|
||||
// Addresses is a list of IP addresses (in CIDR format) that are assigned to the interface.
|
||||
Addresses []string `json:"Addresses" binding:"omitempty,dive,cidr" example:"10.11.12.1/24"`
|
||||
// Dns is a list of DNS servers that should be set if the interface is up.
|
||||
Dns []string `json:"Dns" binding:"omitempty,dive,ip" example:"1.1.1.1"`
|
||||
// DnsSearch is the dns search option string that should be set if the interface is up, will be appended to Dns servers.
|
||||
DnsSearch []string `json:"DnsSearch" binding:"omitempty,dive,fqdn" example:"wg.local"`
|
||||
// Mtu is the device MTU of the interface.
|
||||
Mtu int `json:"Mtu" binding:"omitempty,min=1,max=9000" example:"1420"`
|
||||
// FirewallMark is an optional firewall mark which is used to handle interface traffic.
|
||||
FirewallMark uint32 `json:"FirewallMark"`
|
||||
// RoutingTable is an optional routing table which is used to route interface traffic.
|
||||
RoutingTable string `json:"RoutingTable"`
|
||||
|
||||
// PreUp is an optional action that is executed before the device is up.
|
||||
PreUp string `json:"PreUp" example:"echo 'Interface is up'"`
|
||||
// PostUp is an optional action that is executed after the device is up.
|
||||
PostUp string `json:"PostUp" example:"iptables -A FORWARD -i %i -j ACCEPT"`
|
||||
// PreDown is an optional action that is executed before the device is down.
|
||||
PreDown string `json:"PreDown" example:"iptables -D FORWARD -i %i -j ACCEPT"`
|
||||
// PostDown is an optional action that is executed after the device is down.
|
||||
PostDown string `json:"PostDown" example:"echo 'Interface is down'"`
|
||||
|
||||
// PeerDefNetwork specifies the default subnets from which new peers will get their IP addresses. The subnet is specified in CIDR format.
|
||||
PeerDefNetwork []string `json:"PeerDefNetwork" example:"10.11.12.0/24"`
|
||||
// PeerDefDns specifies the default dns servers for a new peer.
|
||||
PeerDefDns []string `json:"PeerDefDns" example:"8.8.8.8"`
|
||||
// PeerDefDnsSearch specifies the default dns search options for a new peer.
|
||||
PeerDefDnsSearch []string `json:"PeerDefDnsSearch" example:"wg.local"`
|
||||
// PeerDefEndpoint specifies the default endpoint for a new peer.
|
||||
PeerDefEndpoint string `json:"PeerDefEndpoint" example:"wg.example.com:51820"`
|
||||
// PeerDefAllowedIPs specifies the default allowed IP addresses for a new peer.
|
||||
PeerDefAllowedIPs []string `json:"PeerDefAllowedIPs" example:"10.11.12.0/24"`
|
||||
// PeerDefMtu specifies the default device MTU for a new peer.
|
||||
PeerDefMtu int `json:"PeerDefMtu" example:"1420"`
|
||||
// PeerDefPersistentKeepalive specifies the default persistent keep-alive value in seconds for a new peer.
|
||||
PeerDefPersistentKeepalive int `json:"PeerDefPersistentKeepalive" example:"25"`
|
||||
// PeerDefFirewallMark specifies the default firewall mark for a new peer.
|
||||
PeerDefFirewallMark uint32 `json:"PeerDefFirewallMark"`
|
||||
// PeerDefRoutingTable specifies the default routing table for a new peer.
|
||||
PeerDefRoutingTable string `json:"PeerDefRoutingTable"`
|
||||
|
||||
// PeerDefPreUp specifies the default action that is executed before the device is up for a new peer.
|
||||
PeerDefPreUp string `json:"PeerDefPreUp"`
|
||||
// PeerDefPostUp specifies the default action that is executed after the device is up for a new peer.
|
||||
PeerDefPostUp string `json:"PeerDefPostUp"`
|
||||
// PeerDefPreDown specifies the default action that is executed before the device is down for a new peer.
|
||||
PeerDefPreDown string `json:"PeerDefPreDown"`
|
||||
// PeerDefPostDown specifies the default action that is executed after the device is down for a new peer.
|
||||
PeerDefPostDown string `json:"PeerDefPostDown"`
|
||||
|
||||
// Calculated values
|
||||
|
||||
// EnabledPeers is the number of enabled peers for this interface. Only enabled peers are able to connect.
|
||||
EnabledPeers int `json:"EnabledPeers" readonly:"true"`
|
||||
// TotalPeers is the total number of peers for this interface.
|
||||
TotalPeers int `json:"TotalPeers" readonly:"true"`
|
||||
}
|
||||
|
||||
func NewInterface(src *domain.Interface, peers []domain.Peer) *Interface {
|
||||
iface := &Interface{
|
||||
Identifier: string(src.Identifier),
|
||||
DisplayName: src.DisplayName,
|
||||
Mode: string(src.Type),
|
||||
PrivateKey: src.PrivateKey,
|
||||
PublicKey: src.PublicKey,
|
||||
Disabled: src.IsDisabled(),
|
||||
DisabledReason: src.DisabledReason,
|
||||
SaveConfig: src.SaveConfig,
|
||||
ListenPort: src.ListenPort,
|
||||
Addresses: domain.CidrsToStringSlice(src.Addresses),
|
||||
Dns: internal.SliceString(src.DnsStr),
|
||||
DnsSearch: internal.SliceString(src.DnsSearchStr),
|
||||
Mtu: src.Mtu,
|
||||
FirewallMark: src.FirewallMark,
|
||||
RoutingTable: src.RoutingTable,
|
||||
PreUp: src.PreUp,
|
||||
PostUp: src.PostUp,
|
||||
PreDown: src.PreDown,
|
||||
PostDown: src.PostDown,
|
||||
PeerDefNetwork: internal.SliceString(src.PeerDefNetworkStr),
|
||||
PeerDefDns: internal.SliceString(src.PeerDefDnsStr),
|
||||
PeerDefDnsSearch: internal.SliceString(src.PeerDefDnsSearchStr),
|
||||
PeerDefEndpoint: src.PeerDefEndpoint,
|
||||
PeerDefAllowedIPs: internal.SliceString(src.PeerDefAllowedIPsStr),
|
||||
PeerDefMtu: src.PeerDefMtu,
|
||||
PeerDefPersistentKeepalive: src.PeerDefPersistentKeepalive,
|
||||
PeerDefFirewallMark: src.PeerDefFirewallMark,
|
||||
PeerDefRoutingTable: src.PeerDefRoutingTable,
|
||||
PeerDefPreUp: src.PeerDefPreUp,
|
||||
PeerDefPostUp: src.PeerDefPostUp,
|
||||
PeerDefPreDown: src.PeerDefPreDown,
|
||||
PeerDefPostDown: src.PeerDefPostDown,
|
||||
|
||||
EnabledPeers: 0,
|
||||
TotalPeers: 0,
|
||||
}
|
||||
|
||||
if len(peers) > 0 {
|
||||
iface.TotalPeers = len(peers)
|
||||
|
||||
activePeers := 0
|
||||
for _, peer := range peers {
|
||||
if !peer.IsDisabled() {
|
||||
activePeers++
|
||||
}
|
||||
}
|
||||
iface.EnabledPeers = activePeers
|
||||
}
|
||||
|
||||
return iface
|
||||
}
|
||||
|
||||
func NewInterfaces(src []domain.Interface, srcPeers [][]domain.Peer) []Interface {
|
||||
results := make([]Interface, len(src))
|
||||
for i := range src {
|
||||
results[i] = *NewInterface(&src[i], srcPeers[i])
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func NewDomainInterface(src *Interface) *domain.Interface {
|
||||
now := time.Now()
|
||||
|
||||
cidrs, _ := domain.CidrsFromArray(src.Addresses)
|
||||
|
||||
res := &domain.Interface{
|
||||
BaseModel: domain.BaseModel{},
|
||||
Identifier: domain.InterfaceIdentifier(src.Identifier),
|
||||
KeyPair: domain.KeyPair{
|
||||
PrivateKey: src.PrivateKey,
|
||||
PublicKey: src.PublicKey,
|
||||
},
|
||||
ListenPort: src.ListenPort,
|
||||
Addresses: cidrs,
|
||||
DnsStr: internal.SliceToString(src.Dns),
|
||||
DnsSearchStr: internal.SliceToString(src.DnsSearch),
|
||||
Mtu: src.Mtu,
|
||||
FirewallMark: src.FirewallMark,
|
||||
RoutingTable: src.RoutingTable,
|
||||
PreUp: src.PreUp,
|
||||
PostUp: src.PostUp,
|
||||
PreDown: src.PreDown,
|
||||
PostDown: src.PostDown,
|
||||
SaveConfig: src.SaveConfig,
|
||||
DisplayName: src.DisplayName,
|
||||
Type: domain.InterfaceType(src.Mode),
|
||||
DriverType: "", // currently unused
|
||||
Disabled: nil, // set below
|
||||
DisabledReason: src.DisabledReason,
|
||||
PeerDefNetworkStr: internal.SliceToString(src.PeerDefNetwork),
|
||||
PeerDefDnsStr: internal.SliceToString(src.PeerDefDns),
|
||||
PeerDefDnsSearchStr: internal.SliceToString(src.PeerDefDnsSearch),
|
||||
PeerDefEndpoint: src.PeerDefEndpoint,
|
||||
PeerDefAllowedIPsStr: internal.SliceToString(src.PeerDefAllowedIPs),
|
||||
PeerDefMtu: src.PeerDefMtu,
|
||||
PeerDefPersistentKeepalive: src.PeerDefPersistentKeepalive,
|
||||
PeerDefFirewallMark: src.PeerDefFirewallMark,
|
||||
PeerDefRoutingTable: src.PeerDefRoutingTable,
|
||||
PeerDefPreUp: src.PeerDefPreUp,
|
||||
PeerDefPostUp: src.PeerDefPostUp,
|
||||
PeerDefPreDown: src.PeerDefPreDown,
|
||||
PeerDefPostDown: src.PeerDefPostDown,
|
||||
}
|
||||
|
||||
if src.Disabled {
|
||||
res.Disabled = &now
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
195
internal/app/api/v1/models/models_peer.go
Normal file
195
internal/app/api/v1/models/models_peer.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/h44z/wg-portal/internal"
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
)
|
||||
|
||||
const ExpiryDateTimeLayout = "\"2006-01-02\""
|
||||
|
||||
type ExpiryDate struct {
|
||||
*time.Time
|
||||
}
|
||||
|
||||
// UnmarshalJSON will unmarshal using 2006-01-02 layout
|
||||
func (d *ExpiryDate) UnmarshalJSON(b []byte) error {
|
||||
if len(b) == 0 || string(b) == "null" || string(b) == "\"\"" {
|
||||
return nil
|
||||
}
|
||||
parsed, err := time.Parse(ExpiryDateTimeLayout, string(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !parsed.IsZero() {
|
||||
d.Time = &parsed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON will marshal using 2006-01-02 layout
|
||||
func (d *ExpiryDate) MarshalJSON() ([]byte, error) {
|
||||
if d == nil || d.Time == nil {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
|
||||
s := d.Format(ExpiryDateTimeLayout)
|
||||
return []byte(s), nil
|
||||
}
|
||||
|
||||
// Peer represents a WireGuard peer entry.
|
||||
type Peer struct {
|
||||
// Identifier is the unique identifier of the peer. It is always equal to the public key of the peer.
|
||||
Identifier string `json:"Identifier" example:"xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=" binding:"required,len=44"`
|
||||
// DisplayName is a nice display name / description for the peer.
|
||||
DisplayName string `json:"DisplayName" example:"My Peer" binding:"omitempty,max=64"`
|
||||
// UserIdentifier is the identifier of the user that owns the peer.
|
||||
UserIdentifier string `json:"UserIdentifier" example:"uid-1234567"`
|
||||
// InterfaceIdentifier is the identifier of the interface the peer is linked to.
|
||||
InterfaceIdentifier string `json:"InterfaceIdentifier" binding:"required" example:"wg0"`
|
||||
// Disabled is a flag that specifies if the peer is enabled or not. Disabled peers are not able to connect.
|
||||
Disabled bool `json:"Disabled" example:"false"`
|
||||
// DisabledReason is the reason why the peer has been disabled.
|
||||
DisabledReason string `json:"DisabledReason" binding:"required_if=Disabled true" example:"This is a reason why the peer has been disabled."`
|
||||
// ExpiresAt is the expiry date of the peer in YYYY-MM-DD format. An expired peer is not able to connect.
|
||||
ExpiresAt ExpiryDate `json:"ExpiresAt,omitempty" binding:"omitempty,datetime=2006-01-02"`
|
||||
// Notes is a note field for peers.
|
||||
Notes string `json:"Notes" example:"This is a note for the peer."`
|
||||
|
||||
// Endpoint is the endpoint address of the peer.
|
||||
Endpoint ConfigOption[string] `json:"Endpoint"`
|
||||
// EndpointPublicKey is the endpoint public key.
|
||||
EndpointPublicKey ConfigOption[string] `json:"EndpointPublicKey"`
|
||||
// AllowedIPs is a list of allowed IP subnets for the peer.
|
||||
AllowedIPs ConfigOption[[]string] `json:"AllowedIPs"`
|
||||
// ExtraAllowedIPs is a list of additional allowed IP subnets for the peer. These allowed IP subnets are added on the server side.
|
||||
ExtraAllowedIPs []string `json:"ExtraAllowedIPs"`
|
||||
// PresharedKey is the optional pre-shared Key of the peer.
|
||||
PresharedKey string `json:"PresharedKey" example:"yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=" binding:"omitempty,len=44"`
|
||||
// PersistentKeepalive is the optional persistent keep-alive interval in seconds.
|
||||
PersistentKeepalive ConfigOption[int] `json:"PersistentKeepalive" binding:"omitempty,gte=0"`
|
||||
|
||||
// PrivateKey is the private Key of the peer.
|
||||
PrivateKey string `json:"PrivateKey" example:"yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=" binding:"required,len=44"`
|
||||
// PublicKey is the public Key of the server peer.
|
||||
PublicKey string `json:"PublicKey" example:"TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0=" binding:"omitempty,len=44"`
|
||||
|
||||
// Mode is the peer interface type (server, client, any).
|
||||
Mode string `json:"Mode" example:"client" binding:"omitempty,oneof=server client any"`
|
||||
|
||||
// Addresses is a list of IP addresses in CIDR format (both IPv4 and IPv6) for the peer.
|
||||
Addresses []string `json:"Addresses" example:"10.11.12.2/24" binding:"omitempty,dive,cidr"`
|
||||
// CheckAliveAddress is an optional ip address or DNS name that is used for ping checks.
|
||||
CheckAliveAddress string `json:"CheckAliveAddress" binding:"omitempty,ip|fqdn" example:"1.1.1.1"`
|
||||
// Dns is a list of DNS servers that should be set if the peer interface is up.
|
||||
Dns ConfigOption[[]string] `json:"Dns"`
|
||||
// DnsSearch is the dns search option string that should be set if the peer interface is up, will be appended to Dns servers.
|
||||
DnsSearch ConfigOption[[]string] `json:"DnsSearch"`
|
||||
// Mtu is the device MTU of the peer.
|
||||
Mtu ConfigOption[int] `json:"Mtu"`
|
||||
// FirewallMark is an optional firewall mark which is used to handle peer traffic.
|
||||
FirewallMark ConfigOption[uint32] `json:"FirewallMark"`
|
||||
// RoutingTable is an optional routing table which is used to route peer traffic.
|
||||
RoutingTable ConfigOption[string] `json:"RoutingTable"`
|
||||
|
||||
// PreUp is an optional action that is executed before the device is up.
|
||||
PreUp ConfigOption[string] `json:"PreUp"`
|
||||
// PostUp is an optional action that is executed after the device is up.
|
||||
PostUp ConfigOption[string] `json:"PostUp"`
|
||||
// PreDown is an optional action that is executed before the device is down.
|
||||
PreDown ConfigOption[string] `json:"PreDown"`
|
||||
// PostDown is an optional action that is executed after the device is down.
|
||||
PostDown ConfigOption[string] `json:"PostDown"`
|
||||
}
|
||||
|
||||
func NewPeer(src *domain.Peer) *Peer {
|
||||
return &Peer{
|
||||
Identifier: string(src.Identifier),
|
||||
DisplayName: src.DisplayName,
|
||||
UserIdentifier: string(src.UserIdentifier),
|
||||
InterfaceIdentifier: string(src.InterfaceIdentifier),
|
||||
Disabled: src.IsDisabled(),
|
||||
DisabledReason: src.DisabledReason,
|
||||
ExpiresAt: ExpiryDate{src.ExpiresAt},
|
||||
Notes: src.Notes,
|
||||
Endpoint: ConfigOptionFromDomain(src.Endpoint),
|
||||
EndpointPublicKey: ConfigOptionFromDomain(src.EndpointPublicKey),
|
||||
AllowedIPs: StringSliceConfigOptionFromDomain(src.AllowedIPsStr),
|
||||
ExtraAllowedIPs: internal.SliceString(src.ExtraAllowedIPsStr),
|
||||
PresharedKey: string(src.PresharedKey),
|
||||
PersistentKeepalive: ConfigOptionFromDomain(src.PersistentKeepalive),
|
||||
PrivateKey: src.Interface.PrivateKey,
|
||||
PublicKey: src.Interface.PublicKey,
|
||||
Mode: string(src.Interface.Type),
|
||||
Addresses: domain.CidrsToStringSlice(src.Interface.Addresses),
|
||||
CheckAliveAddress: src.Interface.CheckAliveAddress,
|
||||
Dns: StringSliceConfigOptionFromDomain(src.Interface.DnsStr),
|
||||
DnsSearch: StringSliceConfigOptionFromDomain(src.Interface.DnsSearchStr),
|
||||
Mtu: ConfigOptionFromDomain(src.Interface.Mtu),
|
||||
FirewallMark: ConfigOptionFromDomain(src.Interface.FirewallMark),
|
||||
RoutingTable: ConfigOptionFromDomain(src.Interface.RoutingTable),
|
||||
PreUp: ConfigOptionFromDomain(src.Interface.PreUp),
|
||||
PostUp: ConfigOptionFromDomain(src.Interface.PostUp),
|
||||
PreDown: ConfigOptionFromDomain(src.Interface.PreDown),
|
||||
PostDown: ConfigOptionFromDomain(src.Interface.PostDown),
|
||||
}
|
||||
}
|
||||
|
||||
func NewPeers(src []domain.Peer) []Peer {
|
||||
results := make([]Peer, len(src))
|
||||
for i := range src {
|
||||
results[i] = *NewPeer(&src[i])
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func NewDomainPeer(src *Peer) *domain.Peer {
|
||||
now := time.Now()
|
||||
|
||||
cidrs, _ := domain.CidrsFromArray(src.Addresses)
|
||||
|
||||
res := &domain.Peer{
|
||||
BaseModel: domain.BaseModel{},
|
||||
Endpoint: ConfigOptionToDomain(src.Endpoint),
|
||||
EndpointPublicKey: ConfigOptionToDomain(src.EndpointPublicKey),
|
||||
AllowedIPsStr: StringSliceConfigOptionToDomain(src.AllowedIPs),
|
||||
ExtraAllowedIPsStr: internal.SliceToString(src.ExtraAllowedIPs),
|
||||
PresharedKey: domain.PreSharedKey(src.PresharedKey),
|
||||
PersistentKeepalive: ConfigOptionToDomain(src.PersistentKeepalive),
|
||||
DisplayName: src.DisplayName,
|
||||
Identifier: domain.PeerIdentifier(src.Identifier),
|
||||
UserIdentifier: domain.UserIdentifier(src.UserIdentifier),
|
||||
InterfaceIdentifier: domain.InterfaceIdentifier(src.InterfaceIdentifier),
|
||||
Disabled: nil, // set below
|
||||
DisabledReason: src.DisabledReason,
|
||||
ExpiresAt: src.ExpiresAt.Time,
|
||||
Notes: src.Notes,
|
||||
Interface: domain.PeerInterfaceConfig{
|
||||
KeyPair: domain.KeyPair{
|
||||
PrivateKey: src.PrivateKey,
|
||||
PublicKey: src.PublicKey,
|
||||
},
|
||||
Type: domain.InterfaceType(src.Mode),
|
||||
Addresses: cidrs,
|
||||
CheckAliveAddress: src.CheckAliveAddress,
|
||||
DnsStr: StringSliceConfigOptionToDomain(src.Dns),
|
||||
DnsSearchStr: StringSliceConfigOptionToDomain(src.DnsSearch),
|
||||
Mtu: ConfigOptionToDomain(src.Mtu),
|
||||
FirewallMark: ConfigOptionToDomain(src.FirewallMark),
|
||||
RoutingTable: ConfigOptionToDomain(src.RoutingTable),
|
||||
PreUp: ConfigOptionToDomain(src.PreUp),
|
||||
PostUp: ConfigOptionToDomain(src.PostUp),
|
||||
PreDown: ConfigOptionToDomain(src.PreDown),
|
||||
PostDown: ConfigOptionToDomain(src.PostDown),
|
||||
},
|
||||
}
|
||||
|
||||
if src.Disabled {
|
||||
res.Disabled = &now
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
75
internal/app/api/v1/models/models_provisioning.go
Normal file
75
internal/app/api/v1/models/models_provisioning.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package models
|
||||
|
||||
import "github.com/h44z/wg-portal/internal/domain"
|
||||
|
||||
// UserInformation represents the information about a user and its linked peers.
|
||||
type UserInformation struct {
|
||||
// UserIdentifier is the unique identifier of the user.
|
||||
UserIdentifier string `json:"UserIdentifier" example:"uid-1234567"`
|
||||
// PeerCount is the number of peers linked to the user.
|
||||
PeerCount int `json:"PeerCount" example:"2"`
|
||||
// Peers is a list of peers linked to the user.
|
||||
Peers []UserInformationPeer `json:"Peers"`
|
||||
}
|
||||
|
||||
// UserInformationPeer represents the information about a peer.
|
||||
type UserInformationPeer struct {
|
||||
// Identifier is the unique identifier of the peer. It equals the public key of the peer.
|
||||
Identifier string `json:"Identifier" example:"peer-1234567"`
|
||||
// DisplayName is a user-defined description of the peer.
|
||||
DisplayName string `json:"DisplayName" example:"My iPhone"`
|
||||
// IPAddresses is a list of IP addresses in CIDR format assigned to the peer.
|
||||
IpAddresses []string `json:"IpAddresses" example:"10.11.12.2/24"`
|
||||
// IsDisabled is a flag that specifies if the peer is enabled or not. Disabled peers are not able to connect.
|
||||
IsDisabled bool `json:"IsDisabled,omitempty" example:"true"`
|
||||
|
||||
// InterfaceIdentifier is the unique identifier of the WireGuard Portal device the peer is connected to.
|
||||
InterfaceIdentifier string `json:"InterfaceIdentifier" example:"wg0"`
|
||||
}
|
||||
|
||||
func NewUserInformation(user *domain.User, peers []domain.Peer) *UserInformation {
|
||||
if user == nil {
|
||||
return &UserInformation{}
|
||||
}
|
||||
|
||||
ui := &UserInformation{
|
||||
UserIdentifier: string(user.Identifier),
|
||||
PeerCount: len(peers),
|
||||
}
|
||||
|
||||
for _, peer := range peers {
|
||||
ui.Peers = append(ui.Peers, NewUserInformationPeer(peer))
|
||||
}
|
||||
|
||||
if len(ui.Peers) == 0 {
|
||||
ui.Peers = []UserInformationPeer{} // Ensure that the JSON output is an empty array instead of null.
|
||||
}
|
||||
|
||||
return ui
|
||||
}
|
||||
|
||||
func NewUserInformationPeer(peer domain.Peer) UserInformationPeer {
|
||||
up := UserInformationPeer{
|
||||
Identifier: string(peer.Identifier),
|
||||
DisplayName: peer.DisplayName,
|
||||
IpAddresses: domain.CidrsToStringSlice(peer.Interface.Addresses),
|
||||
IsDisabled: peer.IsDisabled(),
|
||||
InterfaceIdentifier: string(peer.InterfaceIdentifier),
|
||||
}
|
||||
|
||||
return up
|
||||
}
|
||||
|
||||
// ProvisioningRequest represents a request to provision a new peer.
|
||||
type ProvisioningRequest struct {
|
||||
// InterfaceIdentifier is the identifier of the WireGuard interface the peer should be linked to.
|
||||
InterfaceIdentifier string `json:"InterfaceIdentifier" example:"wg0" binding:"required"`
|
||||
// UserIdentifier is the identifier of the user the peer should be linked to.
|
||||
// If no user identifier is set, the authenticated user is used.
|
||||
UserIdentifier string `json:"UserIdentifier" example:"uid-1234567"`
|
||||
|
||||
// PublicKey is the optional public key of the peer. If no public key is set, a new key pair is generated.
|
||||
PublicKey string `json:"PublicKey" example:"xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=" binding:"omitempty,len=44"`
|
||||
// PresharedKey is the optional pre-shared key of the peer. If no pre-shared key is set, a new key is generated.
|
||||
PresharedKey string `json:"PresharedKey" example:"yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=" binding:"omitempty,len=44"`
|
||||
}
|
125
internal/app/api/v1/models/models_user.go
Normal file
125
internal/app/api/v1/models/models_user.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
)
|
||||
|
||||
// User represents a user in the system.
|
||||
type User struct {
|
||||
// The unique identifier of the user.
|
||||
Identifier string `json:"Identifier" binding:"required,max=64" example:"uid-1234567"`
|
||||
// The email address of the user. This field is optional.
|
||||
Email string `json:"Email" binding:"omitempty,email" example:"test@test.com"`
|
||||
// The source of the user. This field is optional.
|
||||
Source string `json:"Source" binding:"oneof=db" example:"db"`
|
||||
// The name of the authentication provider. This field is read-only.
|
||||
ProviderName string `json:"ProviderName,omitempty" readonly:"true" example:""`
|
||||
// If this field is set, the user is an admin.
|
||||
IsAdmin bool `json:"IsAdmin" binding:"required" example:"false"`
|
||||
|
||||
// The first name of the user. This field is optional.
|
||||
Firstname string `json:"Firstname" example:"Max"`
|
||||
// The last name of the user. This field is optional.
|
||||
Lastname string `json:"Lastname" example:"Muster"`
|
||||
// The phone number of the user. This field is optional.
|
||||
Phone string `json:"Phone" example:"+1234546789"`
|
||||
// The department of the user. This field is optional.
|
||||
Department string `json:"Department" example:"Software Development"`
|
||||
// Additional notes about the user. This field is optional.
|
||||
Notes string `json:"Notes" example:"some sample notes"`
|
||||
|
||||
// The password of the user. This field is never populated on read operations.
|
||||
Password string `json:"Password,omitempty" binding:"omitempty,min=16,max=64" example:""`
|
||||
// If this field is set, the user is disabled.
|
||||
Disabled bool `json:"Disabled" example:"false"`
|
||||
// The reason why the user has been disabled.
|
||||
DisabledReason string `json:"DisabledReason" binding:"required_if=Disabled true" example:""`
|
||||
// If this field is set, the user is locked and thus unable to log in to WireGuard Portal.
|
||||
Locked bool `json:"Locked" example:"false"`
|
||||
// The reason why the user has been locked.
|
||||
LockedReason string `json:"LockedReason" binding:"required_if=Locked true" example:""`
|
||||
|
||||
// The API token of the user. This field is never populated on bulk read operations.
|
||||
ApiToken string `json:"ApiToken,omitempty" binding:"omitempty,min=32,max=64" example:""`
|
||||
// If this field is set, the user is allowed to use the RESTful API. This field is read-only.
|
||||
ApiEnabled bool `json:"ApiEnabled" readonly:"true" example:"false"`
|
||||
|
||||
// The number of peers linked to the user. This field is read-only.
|
||||
PeerCount int `json:"PeerCount" readonly:"true" example:"2"`
|
||||
}
|
||||
|
||||
func NewUser(src *domain.User, exposeCredentials bool) *User {
|
||||
u := &User{
|
||||
Identifier: string(src.Identifier),
|
||||
Email: src.Email,
|
||||
Source: string(src.Source),
|
||||
ProviderName: src.ProviderName,
|
||||
IsAdmin: src.IsAdmin,
|
||||
Firstname: src.Firstname,
|
||||
Lastname: src.Lastname,
|
||||
Phone: src.Phone,
|
||||
Department: src.Department,
|
||||
Notes: src.Notes,
|
||||
Password: "", // never fill password
|
||||
Disabled: src.IsDisabled(),
|
||||
DisabledReason: src.DisabledReason,
|
||||
Locked: src.IsLocked(),
|
||||
LockedReason: src.LockedReason,
|
||||
ApiToken: "", // by default, do not expose API token
|
||||
ApiEnabled: src.IsApiEnabled(),
|
||||
PeerCount: src.LinkedPeerCount,
|
||||
}
|
||||
|
||||
if exposeCredentials {
|
||||
u.ApiToken = src.ApiToken
|
||||
}
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
func NewUsers(src []domain.User) []User {
|
||||
results := make([]User, len(src))
|
||||
for i := range src {
|
||||
results[i] = *NewUser(&src[i], false)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func NewDomainUser(src *User) *domain.User {
|
||||
now := time.Now()
|
||||
res := &domain.User{
|
||||
Identifier: domain.UserIdentifier(src.Identifier),
|
||||
Email: src.Email,
|
||||
Source: domain.UserSource(src.Source),
|
||||
ProviderName: src.ProviderName,
|
||||
IsAdmin: src.IsAdmin,
|
||||
Firstname: src.Firstname,
|
||||
Lastname: src.Lastname,
|
||||
Phone: src.Phone,
|
||||
Department: src.Department,
|
||||
Notes: src.Notes,
|
||||
Password: domain.PrivateString(src.Password),
|
||||
Disabled: nil, // set below
|
||||
DisabledReason: src.DisabledReason,
|
||||
Locked: nil, // set below
|
||||
LockedReason: src.LockedReason,
|
||||
}
|
||||
|
||||
if src.ApiToken != "" {
|
||||
res.ApiToken = src.ApiToken
|
||||
res.ApiTokenCreated = &now
|
||||
}
|
||||
|
||||
if src.Disabled {
|
||||
res.Disabled = &now
|
||||
}
|
||||
|
||||
if src.Locked {
|
||||
res.Locked = &now
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
Reference in New Issue
Block a user