mirror of https://github.com/h44z/wg-portal.git
				
				
				
			warning: existing webhook receivers need to be adapted to the new models
This commit is contained in:
		
							parent
							
								
									588bbca141
								
							
						
					
					
						commit
						edb88b5768
					
				|  | @ -673,19 +673,6 @@ Without a valid `external_url`, the login process may fail due to CSRF protectio | ||||||
| ## Webhook | ## Webhook | ||||||
| 
 | 
 | ||||||
| The webhook section allows you to configure a webhook that is called on certain events in WireGuard Portal. | The webhook section allows you to configure a webhook that is called on certain events in WireGuard Portal. | ||||||
| A JSON object is sent in a POST request to the webhook URL with the following structure: |  | ||||||
| ```json |  | ||||||
| { |  | ||||||
|   "event": "update", |  | ||||||
|   "entity": "peer", |  | ||||||
|   "identifier": "the-peer-identifier", |  | ||||||
|   "payload": { |  | ||||||
|     // The payload of the event, e.g. peer data. |  | ||||||
|     // Check the API documentation for the exact structure. |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| Further details can be found in the [usage documentation](../usage/webhooks.md). | Further details can be found in the [usage documentation](../usage/webhooks.md). | ||||||
| 
 | 
 | ||||||
| ### `url` | ### `url` | ||||||
|  |  | ||||||
|  | @ -38,11 +38,12 @@ WireGuard Portal supports various events that can trigger webhooks. The followin | ||||||
| - `connect`: Triggered when a user connects to the VPN. | - `connect`: Triggered when a user connects to the VPN. | ||||||
| - `disconnect`: Triggered when a user disconnects from the VPN. | - `disconnect`: Triggered when a user disconnects from the VPN. | ||||||
| 
 | 
 | ||||||
| The following entity types can trigger webhooks: | The following entity models are supported for webhook events: | ||||||
| 
 | 
 | ||||||
| - `user`: When a WireGuard Portal user is created, updated, or deleted. | - `user`: WireGuard Portal users support creation, update, or deletion events. | ||||||
| - `peer`: When a peer is created, updated, or deleted. This entity can also trigger `connect` and `disconnect` events. | - `peer`: Peers support creation, update, or deletion events. Via the `peer_metric` entity, you can also receive connection status updates. | ||||||
| - `interface`: When a device is created, updated, or deleted. | - `peer_metric`: Peer metrics support connection status updates, such as when a peer connects or disconnects. | ||||||
|  | - `interface`: WireGuard interfaces support creation, update, or deletion events. | ||||||
| 
 | 
 | ||||||
| ## Payload Structure | ## Payload Structure | ||||||
| 
 | 
 | ||||||
|  | @ -51,36 +52,234 @@ A common shell structure for webhook payloads is as follows: | ||||||
| 
 | 
 | ||||||
| ```json | ```json | ||||||
| { | { | ||||||
|   "event": "create", |   "event": "create", // The event type, e.g. "create", "update", "delete", "connect", "disconnect" | ||||||
|   "entity": "user", |   "entity": "user",  // The entity type, e.g. "user", "peer", "peer_metric", "interface" | ||||||
|   "identifier": "the-user-identifier", |   "identifier": "the-user-identifier", // Unique identifier of the entity, e.g. user ID or peer ID | ||||||
|   "payload": { |   "payload": { | ||||||
|     // The payload of the event, e.g. peer data. |     // The payload of the event, e.g. a Peer model. | ||||||
|     // Check the API documentation for the exact structure. |     // Detailed model descriptions are provided below. | ||||||
|   } |   } | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | ### Payload Models | ||||||
| 
 | 
 | ||||||
| ### Example Payload | All payload models are encoded as JSON objects. Fields with empty values might be omitted in the payload. | ||||||
|  | 
 | ||||||
|  | #### User Payload (entity: `user`) | ||||||
|  | 
 | ||||||
|  | | JSON Field     | Type        | Description                       | | ||||||
|  | |----------------|-------------|-----------------------------------| | ||||||
|  | | CreatedBy      | string      | Creator identifier                | | ||||||
|  | | UpdatedBy      | string      | Last updater identifier           | | ||||||
|  | | CreatedAt      | time.Time   | Time of creation                  | | ||||||
|  | | UpdatedAt      | time.Time   | Time of last update               | | ||||||
|  | | Identifier     | string      | Unique user identifier            | | ||||||
|  | | Email          | string      | User email                        | | ||||||
|  | | Source         | string      | Authentication source             | | ||||||
|  | | ProviderName   | string      | Name of auth provider             | | ||||||
|  | | IsAdmin        | bool        | Whether user has admin privileges | | ||||||
|  | | Firstname      | string      | User's first name (optional)      | | ||||||
|  | | Lastname       | string      | User's last name (optional)       | | ||||||
|  | | Phone          | string      | Contact phone number (optional)   | | ||||||
|  | | Department     | string      | User's department (optional)      | | ||||||
|  | | Notes          | string      | Additional notes (optional)       | | ||||||
|  | | Disabled       | *time.Time  | When user was disabled            | | ||||||
|  | | DisabledReason | string      | Reason for deactivation           | | ||||||
|  | | Locked         | *time.Time  | When user account was locked      | | ||||||
|  | | LockedReason   | string      | Reason for being locked           | | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | #### Peer Payload (entity: `peer`) | ||||||
|  | 
 | ||||||
|  | | JSON Field           | Type       | Description                            | | ||||||
|  | |----------------------|------------|----------------------------------------| | ||||||
|  | | CreatedBy            | string     | Creator identifier                     | | ||||||
|  | | UpdatedBy            | string     | Last updater identifier                | | ||||||
|  | | CreatedAt            | time.Time  | Creation timestamp                     | | ||||||
|  | | UpdatedAt            | time.Time  | Last update timestamp                  | | ||||||
|  | | Endpoint             | string     | Peer endpoint address                  | | ||||||
|  | | EndpointPublicKey    | string     | Public key of peer endpoint            | | ||||||
|  | | AllowedIPsStr        | string     | Allowed IPs                            | | ||||||
|  | | ExtraAllowedIPsStr   | string     | Extra allowed IPs                      | | ||||||
|  | | PresharedKey         | string     | Pre-shared key for encryption          | | ||||||
|  | | PersistentKeepalive  | int        | Keepalive interval in seconds          | | ||||||
|  | | DisplayName          | string     | Display name of the peer               | | ||||||
|  | | Identifier           | string     | Unique identifier                      | | ||||||
|  | | UserIdentifier       | string     | Associated user ID (optional)          | | ||||||
|  | | InterfaceIdentifier  | string     | Interface this peer is attached to     | | ||||||
|  | | Disabled             | *time.Time | When the peer was disabled             | | ||||||
|  | | DisabledReason       | string     | Reason for being disabled              | | ||||||
|  | | ExpiresAt            | *time.Time | Expiration date                        | | ||||||
|  | | Notes                | string     | Notes for this peer                    | | ||||||
|  | | AutomaticallyCreated | bool       | Whether peer was auto-generated        | | ||||||
|  | | PrivateKey           | string     | Peer private key                       | | ||||||
|  | | PublicKey            | string     | Peer public key                        | | ||||||
|  | | InterfaceType        | string     | Type of the peer interface             | | ||||||
|  | | Addresses            | []string   | IP addresses                           | | ||||||
|  | | CheckAliveAddress    | string     | Address used for alive checks          | | ||||||
|  | | DnsStr               | string     | DNS servers                            | | ||||||
|  | | DnsSearchStr         | string     | DNS search domains                     | | ||||||
|  | | Mtu                  | int        | MTU (Maximum Transmission Unit)        | | ||||||
|  | | FirewallMark         | uint32     | Firewall mark (optional)               | | ||||||
|  | | RoutingTable         | string     | Custom routing table (optional)        | | ||||||
|  | | PreUp                | string     | Command before bringing up interface   | | ||||||
|  | | PostUp               | string     | Command after bringing up interface    | | ||||||
|  | | PreDown              | string     | Command before bringing down interface | | ||||||
|  | | PostDown             | string     | Command after bringing down interface  | | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | #### Interface Payload (entity: `interface`) | ||||||
|  | 
 | ||||||
|  | | JSON Field                 | Type       | Description                            | | ||||||
|  | |----------------------------|------------|----------------------------------------| | ||||||
|  | | CreatedBy                  | string     | Creator identifier                     | | ||||||
|  | | UpdatedBy                  | string     | Last updater identifier                | | ||||||
|  | | CreatedAt                  | time.Time  | Creation timestamp                     | | ||||||
|  | | UpdatedAt                  | time.Time  | Last update timestamp                  | | ||||||
|  | | Identifier                 | string     | Unique identifier                      | | ||||||
|  | | PrivateKey                 | string     | Private key for the interface          | | ||||||
|  | | PublicKey                  | string     | Public key for the interface           | | ||||||
|  | | ListenPort                 | int        | Listening port                         | | ||||||
|  | | Addresses                  | []string   | IP addresses                           | | ||||||
|  | | DnsStr                     | string     | DNS servers                            | | ||||||
|  | | DnsSearchStr               | string     | DNS search domains                     | | ||||||
|  | | Mtu                        | int        | MTU (Maximum Transmission Unit)        | | ||||||
|  | | FirewallMark               | uint32     | Firewall mark                          | | ||||||
|  | | RoutingTable               | string     | Custom routing table                   | | ||||||
|  | | PreUp                      | string     | Command before bringing up interface   | | ||||||
|  | | PostUp                     | string     | Command after bringing up interface    | | ||||||
|  | | PreDown                    | string     | Command before bringing down interface | | ||||||
|  | | PostDown                   | string     | Command after bringing down interface  | | ||||||
|  | | SaveConfig                 | bool       | Whether to save config to file         | | ||||||
|  | | DisplayName                | string     | Human-readable name                    | | ||||||
|  | | Type                       | string     | Type of interface                      | | ||||||
|  | | DriverType                 | string     | Driver used                            | | ||||||
|  | | Disabled                   | *time.Time | When the interface was disabled        | | ||||||
|  | | DisabledReason             | string     | Reason for being disabled              | | ||||||
|  | | PeerDefNetworkStr          | string     | Default peer network configuration     | | ||||||
|  | | PeerDefDnsStr              | string     | Default peer DNS servers               | | ||||||
|  | | PeerDefDnsSearchStr        | string     | Default peer DNS search domains        | | ||||||
|  | | PeerDefEndpoint            | string     | Default peer endpoint                  | | ||||||
|  | | PeerDefAllowedIPsStr       | string     | Default peer allowed IPs               | | ||||||
|  | | PeerDefMtu                 | int        | Default peer MTU                       | | ||||||
|  | | PeerDefPersistentKeepalive | int        | Default keepalive value                | | ||||||
|  | | PeerDefFirewallMark        | uint32     | Default firewall mark for peers        | | ||||||
|  | | PeerDefRoutingTable        | string     | Default routing table for peers        | | ||||||
|  | | PeerDefPreUp               | string     | Default peer pre-up command            | | ||||||
|  | | PeerDefPostUp              | string     | Default peer post-up command           | | ||||||
|  | | PeerDefPreDown             | string     | Default peer pre-down command          | | ||||||
|  | | PeerDefPostDown            | string     | Default peer post-down command         | | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | #### Peer Metrics Payload (entity: `peer_metric`) | ||||||
|  | 
 | ||||||
|  | | JSON Field | Type       | Description                | | ||||||
|  | |------------|------------|----------------------------| | ||||||
|  | | Status     | PeerStatus | Current status of the peer | | ||||||
|  | | Peer       | Peer       | Peer  data                 | | ||||||
|  | 
 | ||||||
|  | `PeerStatus` sub-structure: | ||||||
|  | 
 | ||||||
|  | | JSON Field       | Type       | Description                  | | ||||||
|  | |------------------|------------|------------------------------| | ||||||
|  | | UpdatedAt        | time.Time  | Time of last status update   | | ||||||
|  | | IsConnected      | bool       | Is peer currently connected  | | ||||||
|  | | IsPingable       | bool       | Can peer be pinged           | | ||||||
|  | | LastPing         | *time.Time | Time of last successful ping | | ||||||
|  | | BytesReceived    | uint64     | Bytes received from peer     | | ||||||
|  | | BytesTransmitted | uint64     | Bytes sent to peer           | | ||||||
|  | | Endpoint         | string     | Last known endpoint          | | ||||||
|  | | LastHandshake    | *time.Time | Last successful handshake    | | ||||||
|  | | LastSessionStart | *time.Time | Time the last session began  | | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ### Example Payloads | ||||||
| 
 | 
 | ||||||
| The following payload is an example of a webhook event when a peer connects to the VPN: | The following payload is an example of a webhook event when a peer connects to the VPN: | ||||||
| 
 | 
 | ||||||
| ```json | ```json | ||||||
| { | { | ||||||
|   "event": "connect", |   "event": "connect", | ||||||
|   "entity": "peer", |   "entity": "peer_metric", | ||||||
|   "identifier": "Fb5TaziAs1WrPBjC/MFbWsIelVXvi0hDKZ3YQM9wmU8=", |   "identifier": "Fb5TaziAs1WrPBjC/MFbWsIelVXvi0hDKZ3YQM9wmU8=", | ||||||
|   "payload": { |   "payload": { | ||||||
|     "PeerId": "Fb5TaziAs1WrPBjC/MFbWsIelVXvi0hDKZ3YQM9wmU8=", |     "Status": { | ||||||
|  |       "UpdatedAt": "2025-06-27T22:20:08.734900034+02:00", | ||||||
|       "IsConnected": true, |       "IsConnected": true, | ||||||
|       "IsPingable": false, |       "IsPingable": false, | ||||||
|     "LastPing": null, |       "BytesReceived": 212, | ||||||
|     "BytesReceived": 1860, |       "BytesTransmitted": 2884, | ||||||
|     "BytesTransmitted": 10824, |       "Endpoint": "10.55.66.77:58756", | ||||||
|     "LastHandshake": "2025-06-26T23:04:33.325216659+02:00", |       "LastHandshake": "2025-06-27T22:19:46.580842776+02:00", | ||||||
|     "Endpoint": "10.55.66.77:33874", |       "LastSessionStart": "2025-06-27T22:19:46.580842776+02:00" | ||||||
|     "LastSessionStart": "2025-06-26T22:50:40.10221606+02:00" |     }, | ||||||
|  |     "Peer": { | ||||||
|  |       "CreatedBy": "admin@wgportal.local", | ||||||
|  |       "UpdatedBy": "admin@wgportal.local", | ||||||
|  |       "CreatedAt": "2025-06-26T21:43:49.251839574+02:00", | ||||||
|  |       "UpdatedAt": "2025-06-27T22:18:39.67763985+02:00", | ||||||
|  |       "Endpoint": "10.55.66.1:51820", | ||||||
|  |       "EndpointPublicKey": "eiVibpi3C2PUPcx2kwA5s09OgHx7AEaKMd33k0LQ5mM=", | ||||||
|  |       "AllowedIPsStr": "10.11.12.0/24,fdfd:d3ad:c0de:1234::/64", | ||||||
|  |       "ExtraAllowedIPsStr": "", | ||||||
|  |       "PresharedKey": "p9DDeLUSLOdQcjS8ZsBAiqUzwDIUvTyzavRZFuzhvyE=", | ||||||
|  |       "PersistentKeepalive": 16, | ||||||
|  |       "DisplayName": "Peer Fb5TaziA", | ||||||
|  |       "Identifier": "Fb5TaziAs1WrPBjC/MFbWsIelVXvi0hDKZ3YQM9wmU8=", | ||||||
|  |       "UserIdentifier": "admin@wgportal.local", | ||||||
|  |       "InterfaceIdentifier": "wgTesting", | ||||||
|  |       "AutomaticallyCreated": false, | ||||||
|  |       "PrivateKey": "QBFNBe+7J49ergH0ze2TGUJMFrL/2bOL50Z2cgluYW8=", | ||||||
|  |       "PublicKey": "Fb5TaziAs1WrPBjC/MFbWsIelVXvi0hDKZ3YQM9wmU8=", | ||||||
|  |       "InterfaceType": "client", | ||||||
|  |       "Addresses": [ | ||||||
|  |         "10.11.12.10/32", | ||||||
|  |         "fdfd:d3ad:c0de:1234::a/128" | ||||||
|  |       ], | ||||||
|  |       "CheckAliveAddress": "", | ||||||
|  |       "DnsStr": "", | ||||||
|  |       "DnsSearchStr": "", | ||||||
|  |       "Mtu": 1420 | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Here is another example of a webhook event when a peer is updated: | ||||||
|  | 
 | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "event": "update", | ||||||
|  |   "entity": "peer", | ||||||
|  |   "identifier": "Fb5TaziAs1WrPBjC/MFbWsIelVXvi0hDKZ3YQM9wmU8=", | ||||||
|  |   "payload": { | ||||||
|  |     "CreatedBy": "admin@wgportal.local", | ||||||
|  |     "UpdatedBy": "admin@wgportal.local", | ||||||
|  |     "CreatedAt": "2025-06-26T21:43:49.251839574+02:00", | ||||||
|  |     "UpdatedAt": "2025-06-27T22:18:39.67763985+02:00", | ||||||
|  |     "Endpoint": "10.55.66.1:51820", | ||||||
|  |     "EndpointPublicKey": "eiVibpi3C2PUPcx2kwA5s09OgHx7AEaKMd33k0LQ5mM=", | ||||||
|  |     "AllowedIPsStr": "10.11.12.0/24,fdfd:d3ad:c0de:1234::/64", | ||||||
|  |     "ExtraAllowedIPsStr": "", | ||||||
|  |     "PresharedKey": "p9DDeLUSLOdQcjS8ZsBAiqUzwDIUvTyzavRZFuzhvyE=", | ||||||
|  |     "PersistentKeepalive": 16, | ||||||
|  |     "DisplayName": "Peer Fb5TaziA", | ||||||
|  |     "Identifier": "Fb5TaziAs1WrPBjC/MFbWsIelVXvi0hDKZ3YQM9wmU8=", | ||||||
|  |     "UserIdentifier": "admin@wgportal.local", | ||||||
|  |     "InterfaceIdentifier": "wgTesting", | ||||||
|  |     "AutomaticallyCreated": false, | ||||||
|  |     "PrivateKey": "QBFNBe+7J49ergH0ze2TGUJMFrL/2bOL50Z2cgluYW8=", | ||||||
|  |     "PublicKey": "Fb5TaziAs1WrPBjC/MFbWsIelVXvi0hDKZ3YQM9wmU8=", | ||||||
|  |     "InterfaceType": "client", | ||||||
|  |     "Addresses": [ | ||||||
|  |       "10.11.12.10/32", | ||||||
|  |       "fdfd:d3ad:c0de:1234::a/128" | ||||||
|  |     ], | ||||||
|  |     "CheckAliveAddress": "", | ||||||
|  |     "DnsStr": "", | ||||||
|  |     "DnsSearchStr": "", | ||||||
|  |     "Mtu": 1420 | ||||||
|   } |   } | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
|  | @ -8,6 +8,7 @@ import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 
 | 
 | ||||||
| 	"github.com/h44z/wg-portal/internal/app" | 	"github.com/h44z/wg-portal/internal/app" | ||||||
|  | 	"github.com/h44z/wg-portal/internal/app/webhooks/models" | ||||||
| 	"github.com/h44z/wg-portal/internal/config" | 	"github.com/h44z/wg-portal/internal/config" | ||||||
| 	"github.com/h44z/wg-portal/internal/domain" | 	"github.com/h44z/wg-portal/internal/domain" | ||||||
| ) | ) | ||||||
|  | @ -101,46 +102,46 @@ func (m Manager) sendWebhook(ctx context.Context, data io.Reader) error { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m Manager) handleUserCreateEvent(user domain.User) { | func (m Manager) handleUserCreateEvent(user domain.User) { | ||||||
| 	m.handleGenericEvent(WebhookEventCreate, user) | 	m.handleGenericEvent(WebhookEventCreate, models.NewUser(user)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m Manager) handleUserUpdateEvent(user domain.User) { | func (m Manager) handleUserUpdateEvent(user domain.User) { | ||||||
| 	m.handleGenericEvent(WebhookEventUpdate, user) | 	m.handleGenericEvent(WebhookEventUpdate, models.NewUser(user)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m Manager) handleUserDeleteEvent(user domain.User) { | func (m Manager) handleUserDeleteEvent(user domain.User) { | ||||||
| 	m.handleGenericEvent(WebhookEventDelete, user) | 	m.handleGenericEvent(WebhookEventDelete, models.NewUser(user)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m Manager) handlePeerCreateEvent(peer domain.Peer) { | func (m Manager) handlePeerCreateEvent(peer domain.Peer) { | ||||||
| 	m.handleGenericEvent(WebhookEventCreate, peer) | 	m.handleGenericEvent(WebhookEventCreate, models.NewPeer(peer)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m Manager) handlePeerUpdateEvent(peer domain.Peer) { | func (m Manager) handlePeerUpdateEvent(peer domain.Peer) { | ||||||
| 	m.handleGenericEvent(WebhookEventUpdate, peer) | 	m.handleGenericEvent(WebhookEventUpdate, models.NewPeer(peer)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m Manager) handlePeerDeleteEvent(peer domain.Peer) { | func (m Manager) handlePeerDeleteEvent(peer domain.Peer) { | ||||||
| 	m.handleGenericEvent(WebhookEventDelete, peer) | 	m.handleGenericEvent(WebhookEventDelete, models.NewPeer(peer)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m Manager) handleInterfaceCreateEvent(iface domain.Interface) { | func (m Manager) handleInterfaceCreateEvent(iface domain.Interface) { | ||||||
| 	m.handleGenericEvent(WebhookEventCreate, iface) | 	m.handleGenericEvent(WebhookEventCreate, models.NewInterface(iface)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m Manager) handleInterfaceUpdateEvent(iface domain.Interface) { | func (m Manager) handleInterfaceUpdateEvent(iface domain.Interface) { | ||||||
| 	m.handleGenericEvent(WebhookEventUpdate, iface) | 	m.handleGenericEvent(WebhookEventUpdate, models.NewInterface(iface)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m Manager) handleInterfaceDeleteEvent(iface domain.Interface) { | func (m Manager) handleInterfaceDeleteEvent(iface domain.Interface) { | ||||||
| 	m.handleGenericEvent(WebhookEventDelete, iface) | 	m.handleGenericEvent(WebhookEventDelete, models.NewInterface(iface)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m Manager) handlePeerStateChangeEvent(peerStatus domain.PeerStatus) { | func (m Manager) handlePeerStateChangeEvent(peerStatus domain.PeerStatus, peer domain.Peer) { | ||||||
| 	if peerStatus.IsConnected { | 	if peerStatus.IsConnected { | ||||||
| 		m.handleGenericEvent(WebhookEventConnect, peerStatus) | 		m.handleGenericEvent(WebhookEventConnect, models.NewPeerMetrics(peerStatus, peer)) | ||||||
| 	} else { | 	} else { | ||||||
| 		m.handleGenericEvent(WebhookEventDisconnect, peerStatus) | 		m.handleGenericEvent(WebhookEventDisconnect, models.NewPeerMetrics(peerStatus, peer)) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -177,18 +178,18 @@ func (m Manager) createWebhookData(action WebhookEvent, payload any) (*WebhookDa | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	switch v := payload.(type) { | 	switch v := payload.(type) { | ||||||
| 	case domain.User: | 	case models.User: | ||||||
| 		d.Entity = WebhookEntityUser | 		d.Entity = WebhookEntityUser | ||||||
| 		d.Identifier = string(v.Identifier) | 		d.Identifier = v.Identifier | ||||||
| 	case domain.Peer: | 	case models.Peer: | ||||||
| 		d.Entity = WebhookEntityPeer | 		d.Entity = WebhookEntityPeer | ||||||
| 		d.Identifier = string(v.Identifier) | 		d.Identifier = v.Identifier | ||||||
| 	case domain.Interface: | 	case models.Interface: | ||||||
| 		d.Entity = WebhookEntityInterface | 		d.Entity = WebhookEntityInterface | ||||||
| 		d.Identifier = string(v.Identifier) | 		d.Identifier = v.Identifier | ||||||
| 	case domain.PeerStatus: | 	case models.PeerMetrics: | ||||||
| 		d.Entity = WebhookEntityPeer | 		d.Entity = WebhookEntityPeerMetric | ||||||
| 		d.Identifier = string(v.PeerId) | 		d.Identifier = v.Peer.Identifier | ||||||
| 	default: | 	default: | ||||||
| 		return nil, fmt.Errorf("unsupported payload type: %T", v) | 		return nil, fmt.Errorf("unsupported payload type: %T", v) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -36,6 +36,7 @@ type WebhookEntity = string | ||||||
| const ( | const ( | ||||||
| 	WebhookEntityUser       WebhookEntity = "user" | 	WebhookEntityUser       WebhookEntity = "user" | ||||||
| 	WebhookEntityPeer       WebhookEntity = "peer" | 	WebhookEntityPeer       WebhookEntity = "peer" | ||||||
|  | 	WebhookEntityPeerMetric WebhookEntity = "peer_metric" | ||||||
| 	WebhookEntityInterface  WebhookEntity = "interface" | 	WebhookEntityInterface  WebhookEntity = "interface" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,99 @@ | ||||||
|  | package models | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/h44z/wg-portal/internal/domain" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Interface represents an interface model for webhooks. For details about the fields, see the domain.Interface struct.
 | ||||||
|  | type Interface struct { | ||||||
|  | 	CreatedBy string    `json:"CreatedBy"` | ||||||
|  | 	UpdatedBy string    `json:"UpdatedBy"` | ||||||
|  | 	CreatedAt time.Time `json:"CreatedAt"` | ||||||
|  | 	UpdatedAt time.Time `json:"UpdatedAt"` | ||||||
|  | 
 | ||||||
|  | 	Identifier string `json:"Identifier"` | ||||||
|  | 	PrivateKey string `json:"PrivateKey"` | ||||||
|  | 	PublicKey  string `json:"PublicKey"` | ||||||
|  | 	ListenPort int    `json:"ListenPort"` | ||||||
|  | 
 | ||||||
|  | 	Addresses    []string `json:"Addresses"` | ||||||
|  | 	DnsStr       string   `json:"DnsStr"` | ||||||
|  | 	DnsSearchStr string   `json:"DnsSearchStr"` | ||||||
|  | 
 | ||||||
|  | 	Mtu          int    `json:"Mtu"` | ||||||
|  | 	FirewallMark uint32 `json:"FirewallMark"` | ||||||
|  | 	RoutingTable string `json:"RoutingTable"` | ||||||
|  | 
 | ||||||
|  | 	PreUp    string `json:"PreUp"` | ||||||
|  | 	PostUp   string `json:"PostUp"` | ||||||
|  | 	PreDown  string `json:"PreDown"` | ||||||
|  | 	PostDown string `json:"PostDown"` | ||||||
|  | 
 | ||||||
|  | 	SaveConfig bool `json:"SaveConfig"` | ||||||
|  | 
 | ||||||
|  | 	DisplayName    string     `json:"DisplayName"` | ||||||
|  | 	Type           string     `json:"Type"` | ||||||
|  | 	DriverType     string     `json:"DriverType"` | ||||||
|  | 	Disabled       *time.Time `json:"Disabled,omitempty"` | ||||||
|  | 	DisabledReason string     `json:"DisabledReason,omitempty"` | ||||||
|  | 
 | ||||||
|  | 	PeerDefNetworkStr          string `json:"PeerDefNetworkStr,omitempty"` | ||||||
|  | 	PeerDefDnsStr              string `json:"PeerDefDnsStr,omitempty"` | ||||||
|  | 	PeerDefDnsSearchStr        string `json:"PeerDefDnsSearchStr,omitempty"` | ||||||
|  | 	PeerDefEndpoint            string `json:"PeerDefEndpoint,omitempty"` | ||||||
|  | 	PeerDefAllowedIPsStr       string `json:"PeerDefAllowedIPsStr,omitempty"` | ||||||
|  | 	PeerDefMtu                 int    `json:"PeerDefMtu,omitempty"` | ||||||
|  | 	PeerDefPersistentKeepalive int    `json:"PeerDefPersistentKeepalive,omitempty"` | ||||||
|  | 	PeerDefFirewallMark        uint32 `json:"PeerDefFirewallMark,omitempty"` | ||||||
|  | 	PeerDefRoutingTable        string `json:"PeerDefRoutingTable,omitempty"` | ||||||
|  | 
 | ||||||
|  | 	PeerDefPreUp    string `json:"PeerDefPreUp,omitempty"` | ||||||
|  | 	PeerDefPostUp   string `json:"PeerDefPostUp,omitempty"` | ||||||
|  | 	PeerDefPreDown  string `json:"PeerDefPreDown,omitempty"` | ||||||
|  | 	PeerDefPostDown string `json:"PeerDefPostDown,omitempty"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewInterface creates a new Interface model from a domain.Interface.
 | ||||||
|  | func NewInterface(src domain.Interface) Interface { | ||||||
|  | 	return Interface{ | ||||||
|  | 		CreatedBy:                  src.CreatedBy, | ||||||
|  | 		UpdatedBy:                  src.UpdatedBy, | ||||||
|  | 		CreatedAt:                  src.CreatedAt, | ||||||
|  | 		UpdatedAt:                  src.UpdatedAt, | ||||||
|  | 		Identifier:                 string(src.Identifier), | ||||||
|  | 		PrivateKey:                 src.KeyPair.PrivateKey, | ||||||
|  | 		PublicKey:                  src.KeyPair.PublicKey, | ||||||
|  | 		ListenPort:                 src.ListenPort, | ||||||
|  | 		Addresses:                  domain.CidrsToStringSlice(src.Addresses), | ||||||
|  | 		DnsStr:                     src.DnsStr, | ||||||
|  | 		DnsSearchStr:               src.DnsSearchStr, | ||||||
|  | 		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:                string(src.Identifier), | ||||||
|  | 		Type:                       string(src.Type), | ||||||
|  | 		DriverType:                 src.DriverType, | ||||||
|  | 		Disabled:                   src.Disabled, | ||||||
|  | 		DisabledReason:             src.DisabledReason, | ||||||
|  | 		PeerDefNetworkStr:          src.PeerDefNetworkStr, | ||||||
|  | 		PeerDefDnsStr:              src.PeerDefDnsStr, | ||||||
|  | 		PeerDefDnsSearchStr:        src.PeerDefDnsSearchStr, | ||||||
|  | 		PeerDefEndpoint:            src.PeerDefEndpoint, | ||||||
|  | 		PeerDefAllowedIPsStr:       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, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,89 @@ | ||||||
|  | package models | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/h44z/wg-portal/internal/domain" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Peer represents a peer model for webhooks.  For details about the fields, see the domain.Peer struct.
 | ||||||
|  | type Peer struct { | ||||||
|  | 	CreatedBy string    `json:"CreatedBy"` | ||||||
|  | 	UpdatedBy string    `json:"UpdatedBy"` | ||||||
|  | 	CreatedAt time.Time `json:"CreatedAt"` | ||||||
|  | 	UpdatedAt time.Time `json:"UpdatedAt"` | ||||||
|  | 
 | ||||||
|  | 	Endpoint            string `json:"Endpoint"` | ||||||
|  | 	EndpointPublicKey   string `json:"EndpointPublicKey"` | ||||||
|  | 	AllowedIPsStr       string `json:"AllowedIPsStr"` | ||||||
|  | 	ExtraAllowedIPsStr  string `json:"ExtraAllowedIPsStr"` | ||||||
|  | 	PresharedKey        string `json:"PresharedKey"` | ||||||
|  | 	PersistentKeepalive int    `json:"PersistentKeepalive"` | ||||||
|  | 
 | ||||||
|  | 	DisplayName          string     `json:"DisplayName"` | ||||||
|  | 	Identifier           string     `json:"Identifier"` | ||||||
|  | 	UserIdentifier       string     `json:"UserIdentifier"` | ||||||
|  | 	InterfaceIdentifier  string     `json:"InterfaceIdentifier"` | ||||||
|  | 	Disabled             *time.Time `json:"Disabled,omitempty"` | ||||||
|  | 	DisabledReason       string     `json:"DisabledReason,omitempty"` | ||||||
|  | 	ExpiresAt            *time.Time `json:"ExpiresAt,omitempty"` | ||||||
|  | 	Notes                string     `json:"Notes,omitempty"` | ||||||
|  | 	AutomaticallyCreated bool       `json:"AutomaticallyCreated"` | ||||||
|  | 
 | ||||||
|  | 	PrivateKey string `json:"PrivateKey"` | ||||||
|  | 	PublicKey  string `json:"PublicKey"` | ||||||
|  | 
 | ||||||
|  | 	InterfaceType string `json:"InterfaceType"` | ||||||
|  | 
 | ||||||
|  | 	Addresses         []string `json:"Addresses"` | ||||||
|  | 	CheckAliveAddress string   `json:"CheckAliveAddress"` | ||||||
|  | 	DnsStr            string   `json:"DnsStr"` | ||||||
|  | 	DnsSearchStr      string   `json:"DnsSearchStr"` | ||||||
|  | 	Mtu               int      `json:"Mtu"` | ||||||
|  | 	FirewallMark      uint32   `json:"FirewallMark,omitempty"` | ||||||
|  | 	RoutingTable      string   `json:"RoutingTable,omitempty"` | ||||||
|  | 
 | ||||||
|  | 	PreUp    string `json:"PreUp,omitempty"` | ||||||
|  | 	PostUp   string `json:"PostUp,omitempty"` | ||||||
|  | 	PreDown  string `json:"PreDown,omitempty"` | ||||||
|  | 	PostDown string `json:"PostDown,omitempty"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewPeer creates a new Peer model from a domain.Peer.
 | ||||||
|  | func NewPeer(src domain.Peer) Peer { | ||||||
|  | 	return Peer{ | ||||||
|  | 		CreatedBy:            src.CreatedBy, | ||||||
|  | 		UpdatedBy:            src.UpdatedBy, | ||||||
|  | 		CreatedAt:            src.CreatedAt, | ||||||
|  | 		UpdatedAt:            src.UpdatedAt, | ||||||
|  | 		Endpoint:             src.Endpoint.GetValue(), | ||||||
|  | 		EndpointPublicKey:    src.EndpointPublicKey.GetValue(), | ||||||
|  | 		AllowedIPsStr:        src.AllowedIPsStr.GetValue(), | ||||||
|  | 		ExtraAllowedIPsStr:   src.ExtraAllowedIPsStr, | ||||||
|  | 		PresharedKey:         string(src.PresharedKey), | ||||||
|  | 		PersistentKeepalive:  src.PersistentKeepalive.GetValue(), | ||||||
|  | 		DisplayName:          src.DisplayName, | ||||||
|  | 		Identifier:           string(src.Identifier), | ||||||
|  | 		UserIdentifier:       string(src.UserIdentifier), | ||||||
|  | 		InterfaceIdentifier:  string(src.InterfaceIdentifier), | ||||||
|  | 		Disabled:             src.Disabled, | ||||||
|  | 		DisabledReason:       src.DisabledReason, | ||||||
|  | 		ExpiresAt:            src.ExpiresAt, | ||||||
|  | 		Notes:                src.Notes, | ||||||
|  | 		AutomaticallyCreated: src.AutomaticallyCreated, | ||||||
|  | 		PrivateKey:           src.Interface.KeyPair.PrivateKey, | ||||||
|  | 		PublicKey:            src.Interface.KeyPair.PublicKey, | ||||||
|  | 		InterfaceType:        string(src.Interface.Type), | ||||||
|  | 		Addresses:            domain.CidrsToStringSlice(src.Interface.Addresses), | ||||||
|  | 		CheckAliveAddress:    src.Interface.CheckAliveAddress, | ||||||
|  | 		DnsStr:               src.Interface.DnsStr.GetValue(), | ||||||
|  | 		DnsSearchStr:         src.Interface.DnsSearchStr.GetValue(), | ||||||
|  | 		Mtu:                  src.Interface.Mtu.GetValue(), | ||||||
|  | 		FirewallMark:         src.Interface.FirewallMark.GetValue(), | ||||||
|  | 		RoutingTable:         src.Interface.RoutingTable.GetValue(), | ||||||
|  | 		PreUp:                src.Interface.PreUp.GetValue(), | ||||||
|  | 		PostUp:               src.Interface.PostUp.GetValue(), | ||||||
|  | 		PreDown:              src.Interface.PreDown.GetValue(), | ||||||
|  | 		PostDown:             src.Interface.PostDown.GetValue(), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,50 @@ | ||||||
|  | package models | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/h44z/wg-portal/internal/domain" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // PeerMetrics represents a peer metrics model for webhooks.
 | ||||||
|  | // For details about the fields, see the domain.PeerStatus and domain.Peer structs.
 | ||||||
|  | type PeerMetrics struct { | ||||||
|  | 	Status PeerStatus `json:"Status"` | ||||||
|  | 	Peer   Peer       `json:"Peer"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // PeerStatus represents the status of a peer for webhooks.
 | ||||||
|  | // For details about the fields, see the domain.PeerStatus struct.
 | ||||||
|  | type PeerStatus struct { | ||||||
|  | 	UpdatedAt time.Time `json:"UpdatedAt"` | ||||||
|  | 
 | ||||||
|  | 	IsConnected bool `json:"IsConnected"` | ||||||
|  | 
 | ||||||
|  | 	IsPingable bool       `json:"IsPingable"` | ||||||
|  | 	LastPing   *time.Time `json:"LastPing,omitempty"` | ||||||
|  | 
 | ||||||
|  | 	BytesReceived    uint64 `json:"BytesReceived"` | ||||||
|  | 	BytesTransmitted uint64 `json:"BytesTransmitted"` | ||||||
|  | 
 | ||||||
|  | 	Endpoint         string     `json:"Endpoint"` | ||||||
|  | 	LastHandshake    *time.Time `json:"LastHandshake,omitempty"` | ||||||
|  | 	LastSessionStart *time.Time `json:"LastSessionStart,omitempty"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewPeerMetrics creates a new PeerMetrics model from the domain.PeerStatus and domain.Peer models.
 | ||||||
|  | func NewPeerMetrics(status domain.PeerStatus, peer domain.Peer) PeerMetrics { | ||||||
|  | 	return PeerMetrics{ | ||||||
|  | 		Status: PeerStatus{ | ||||||
|  | 			UpdatedAt:        status.UpdatedAt, | ||||||
|  | 			IsConnected:      status.IsConnected, | ||||||
|  | 			IsPingable:       status.IsPingable, | ||||||
|  | 			LastPing:         status.LastPing, | ||||||
|  | 			BytesReceived:    status.BytesReceived, | ||||||
|  | 			BytesTransmitted: status.BytesTransmitted, | ||||||
|  | 			Endpoint:         status.Endpoint, | ||||||
|  | 			LastHandshake:    status.LastHandshake, | ||||||
|  | 			LastSessionStart: status.LastSessionStart, | ||||||
|  | 		}, | ||||||
|  | 		Peer: NewPeer(peer), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,56 @@ | ||||||
|  | package models | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/h44z/wg-portal/internal/domain" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // User represents a user model for webhooks. For details about the fields, see the domain.User struct.
 | ||||||
|  | type User struct { | ||||||
|  | 	CreatedBy string    `json:"CreatedBy"` | ||||||
|  | 	UpdatedBy string    `json:"UpdatedBy"` | ||||||
|  | 	CreatedAt time.Time `json:"CreatedAt"` | ||||||
|  | 	UpdatedAt time.Time `json:"UpdatedAt"` | ||||||
|  | 
 | ||||||
|  | 	Identifier   string `json:"Identifier"` | ||||||
|  | 	Email        string `json:"Email"` | ||||||
|  | 	Source       string `json:"Source"` | ||||||
|  | 	ProviderName string `json:"ProviderName"` | ||||||
|  | 	IsAdmin      bool   `json:"IsAdmin"` | ||||||
|  | 
 | ||||||
|  | 	Firstname  string `json:"Firstname,omitempty"` | ||||||
|  | 	Lastname   string `json:"Lastname,omitempty"` | ||||||
|  | 	Phone      string `json:"Phone,omitempty"` | ||||||
|  | 	Department string `json:"Department,omitempty"` | ||||||
|  | 	Notes      string `json:"Notes,omitempty"` | ||||||
|  | 
 | ||||||
|  | 	Disabled       *time.Time `json:"Disabled,omitempty"` | ||||||
|  | 	DisabledReason string     `json:"DisabledReason,omitempty"` | ||||||
|  | 	Locked         *time.Time `json:"Locked,omitempty"` | ||||||
|  | 	LockedReason   string     `json:"LockedReason,omitempty"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewUser creates a new User model from a domain.User
 | ||||||
|  | func NewUser(src domain.User) User { | ||||||
|  | 	return User{ | ||||||
|  | 		CreatedBy:      src.CreatedBy, | ||||||
|  | 		UpdatedBy:      src.UpdatedBy, | ||||||
|  | 		CreatedAt:      src.CreatedAt, | ||||||
|  | 		UpdatedAt:      src.UpdatedAt, | ||||||
|  | 		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, | ||||||
|  | 		Disabled:       src.Disabled, | ||||||
|  | 		DisabledReason: src.DisabledReason, | ||||||
|  | 		Locked:         src.Locked, | ||||||
|  | 		LockedReason:   src.LockedReason, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -213,8 +213,14 @@ func (c *StatisticsCollector) collectPeerData(ctx context.Context) { | ||||||
| 					} | 					} | ||||||
| 
 | 
 | ||||||
| 					if connectionStateChanged { | 					if connectionStateChanged { | ||||||
|  | 						peerModel, err := c.db.GetPeer(ctx, peer.Identifier) | ||||||
|  | 						if err != nil { | ||||||
|  | 							slog.Error("failed to fetch peer for data collection", "peer", peer.Identifier, "error", | ||||||
|  | 								err) | ||||||
|  | 							continue | ||||||
|  | 						} | ||||||
| 						// publish event if connection state changed
 | 						// publish event if connection state changed
 | ||||||
| 						c.bus.Publish(app.TopicPeerStateChanged, newPeerStatus) | 						c.bus.Publish(app.TopicPeerStateChanged, newPeerStatus, *peerModel) | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | @ -356,7 +362,7 @@ func (c *StatisticsCollector) pingWorker(ctx context.Context) { | ||||||
| 
 | 
 | ||||||
| 		if connectionStateChanged { | 		if connectionStateChanged { | ||||||
| 			// publish event if connection state changed
 | 			// publish event if connection state changed
 | ||||||
| 			c.bus.Publish(app.TopicPeerStateChanged, newPeerStatus) | 			c.bus.Publish(app.TopicPeerStateChanged, newPeerStatus, peer) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue