[management] Add notification endpoints (#5590)

This commit is contained in:
Bethuel Mmbaga
2026-03-26 18:26:33 +03:00
committed by GitHub
parent 145d82f322
commit 7be8752a00
5 changed files with 591 additions and 0 deletions

View File

@@ -5494,3 +5494,61 @@ func (s *SqlStore) CleanupStaleProxies(ctx context.Context, inactivityDuration t
return nil
}
// GetRoutingPeerNetworks returns the distinct network names where the peer is assigned as a routing peer
// in an enabled network router, either directly or via peer groups.
func (s *SqlStore) GetRoutingPeerNetworks(_ context.Context, accountID, peerID string) ([]string, error) {
var routers []*routerTypes.NetworkRouter
if err := s.db.Select("peer, peer_groups, network_id").Where("account_id = ? AND enabled = true", accountID).Find(&routers).Error; err != nil {
return nil, status.Errorf(status.Internal, "failed to get enabled routers: %v", err)
}
if len(routers) == 0 {
return nil, nil
}
var groupPeers []types.GroupPeer
if err := s.db.Select("group_id").Where("account_id = ? AND peer_id = ?", accountID, peerID).Find(&groupPeers).Error; err != nil {
return nil, status.Errorf(status.Internal, "failed to get peer group memberships: %v", err)
}
groupSet := make(map[string]struct{}, len(groupPeers))
for _, gp := range groupPeers {
groupSet[gp.GroupID] = struct{}{}
}
networkIDs := make(map[string]struct{})
for _, r := range routers {
if r.Peer == peerID {
networkIDs[r.NetworkID] = struct{}{}
} else if r.Peer == "" {
for _, pg := range r.PeerGroups {
if _, ok := groupSet[pg]; ok {
networkIDs[r.NetworkID] = struct{}{}
break
}
}
}
}
if len(networkIDs) == 0 {
return nil, nil
}
ids := make([]string, 0, len(networkIDs))
for id := range networkIDs {
ids = append(ids, id)
}
var networks []*networkTypes.Network
if err := s.db.Select("name").Where("account_id = ? AND id IN ?", accountID, ids).Find(&networks).Error; err != nil {
return nil, status.Errorf(status.Internal, "failed to get networks: %v", err)
}
names := make([]string, 0, len(networks))
for _, n := range networks {
names = append(names, n.Name)
}
return names, nil
}

View File

@@ -290,6 +290,8 @@ type Store interface {
CleanupStaleProxies(ctx context.Context, inactivityDuration time.Duration) error
GetCustomDomainsCounts(ctx context.Context) (total int64, validated int64, err error)
GetRoutingPeerNetworks(ctx context.Context, accountID, peerID string) ([]string, error)
}
const (

View File

@@ -2333,6 +2333,21 @@ func (mr *MockStoreMockRecorder) IncrementSetupKeyUsage(ctx, setupKeyID interfac
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IncrementSetupKeyUsage", reflect.TypeOf((*MockStore)(nil).IncrementSetupKeyUsage), ctx, setupKeyID)
}
// GetRoutingPeerNetworks mocks base method.
func (m *MockStore) GetRoutingPeerNetworks(ctx context.Context, accountID, peerID string) ([]string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetRoutingPeerNetworks", ctx, accountID, peerID)
ret0, _ := ret[0].([]string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetRoutingPeerNetworks indicates an expected call of GetRoutingPeerNetworks.
func (mr *MockStoreMockRecorder) GetRoutingPeerNetworks(ctx, accountID, peerID interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRoutingPeerNetworks", reflect.TypeOf((*MockStore)(nil).GetRoutingPeerNetworks), ctx, accountID, peerID)
}
// IsPrimaryAccount mocks base method.
func (m *MockStore) IsPrimaryAccount(ctx context.Context, accountID string) (bool, string, error) {
m.ctrl.T.Helper()

View File

@@ -89,6 +89,10 @@ tags:
- name: Event Streaming Integrations
description: Manage event streaming integrations.
x-cloud-only: true
- name: Notifications
description: Manage notification channels for account event alerts.
x-cloud-only: true
components:
schemas:
@@ -4385,6 +4389,123 @@ components:
type: string
description: The newly generated SCIM API token
example: "nbs_F3f0d..."
NotificationChannelType:
type: string
description: The type of notification channel.
enum:
- email
- webhook
example: "email"
NotificationEventType:
type: string
description: |
An activity event type code. See `GET /api/integrations/notifications/types` for the full list
of supported event types and their human-readable descriptions.
example: "user.join"
EmailTarget:
type: object
description: Target configuration for email notification channels.
properties:
emails:
type: array
description: List of email addresses to send notifications to.
minItems: 1
items:
type: string
format: email
example: [ "admin@example.com", "ops@example.com" ]
required:
- emails
WebhookTarget:
type: object
description: Target configuration for webhook notification channels.
properties:
url:
type: string
format: uri
description: The webhook endpoint URL to send notifications to.
example: "https://hooks.example.com/netbird"
headers:
type: object
additionalProperties:
type: string
description: |
Custom HTTP headers sent with each webhook request.
Values are write-only; in GET responses all values are masked.
example:
Authorization: "Bearer token"
X-Webhook-Secret: "secret"
required:
- url
NotificationChannelRequest:
type: object
description: Request body for creating or updating a notification channel.
properties:
type:
$ref: '#/components/schemas/NotificationChannelType'
target:
description: |
Channel-specific target configuration. The shape depends on the `type` field:
- `email`: requires an `EmailTarget` object
- `webhook`: requires a `WebhookTarget` object
oneOf:
- $ref: '#/components/schemas/EmailTarget'
- $ref: '#/components/schemas/WebhookTarget'
event_types:
type: array
description: List of activity event type codes this channel subscribes to.
items:
$ref: '#/components/schemas/NotificationEventType'
example: [ "user.join", "peer.user.add", "peer.login.expire" ]
enabled:
type: boolean
description: Whether this notification channel is active.
example: true
required:
- type
- event_types
- enabled
NotificationChannelResponse:
type: object
description: A notification channel configuration.
properties:
id:
type: string
description: Unique identifier of the notification channel.
readOnly: true
example: "ch8i4ug6lnn4g9hqv7m0"
type:
$ref: '#/components/schemas/NotificationChannelType'
target:
description: |
Channel-specific target configuration. The shape depends on the `type` field:
- `email`: an `EmailTarget` object
- `webhook`: a `WebhookTarget` object
oneOf:
- $ref: '#/components/schemas/EmailTarget'
- $ref: '#/components/schemas/WebhookTarget'
event_types:
type: array
description: List of activity event type codes this channel subscribes to.
items:
$ref: '#/components/schemas/NotificationEventType'
example: [ "user.join", "peer.user.add", "peer.login.expire" ]
enabled:
type: boolean
description: Whether this notification channel is active.
example: true
required:
- id
- type
- event_types
- enabled
NotificationTypeEntry:
type: object
description: A map of event type codes to their human-readable descriptions.
additionalProperties:
type: string
example:
user.join: "User joined"
BypassResponse:
type: object
description: Response for bypassed peer operations.
@@ -10062,3 +10183,172 @@ paths:
"$ref": "#/components/responses/not_found"
'500':
"$ref": "#/components/responses/internal_error"
/api/integrations/notifications/types:
get:
tags:
- Notifications
summary: List Notification Event Types
description: |
Returns a map of all supported activity event type codes to their
human-readable descriptions. Use these codes when configuring
`event_types` on notification channels.
operationId: listNotificationEventTypes
responses:
'200':
description: A map of event type codes to descriptions.
content:
application/json:
schema:
$ref: '#/components/schemas/NotificationTypeEntry'
'400':
"$ref": "#/components/responses/bad_request"
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'404':
"$ref": "#/components/responses/not_found"
'500':
"$ref": "#/components/responses/internal_error"
/api/integrations/notifications/channels:
get:
tags:
- Notifications
summary: List Notification Channels
description: Retrieves all notification channels configured for the authenticated account.
operationId: listNotificationChannels
responses:
'200':
description: A list of notification channels.
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/NotificationChannelResponse'
'400':
"$ref": "#/components/responses/bad_request"
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'404':
"$ref": "#/components/responses/not_found"
'500':
"$ref": "#/components/responses/internal_error"
post:
tags:
- Notifications
summary: Create Notification Channel
description: |
Creates a new notification channel for the authenticated account.
Supported channel types are `email` and `webhook`.
operationId: createNotificationChannel
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NotificationChannelRequest'
responses:
'200':
description: Notification channel created successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/NotificationChannelResponse'
'400':
"$ref": "#/components/responses/bad_request"
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'404':
"$ref": "#/components/responses/not_found"
'500':
"$ref": "#/components/responses/internal_error"
/api/integrations/notifications/channels/{channelId}:
parameters:
- name: channelId
in: path
required: true
description: The unique identifier of the notification channel.
schema:
type: string
example: "ch8i4ug6lnn4g9hqv7m0"
get:
tags:
- Notifications
summary: Get Notification Channel
description: Retrieves a specific notification channel by its ID.
operationId: getNotificationChannel
responses:
'200':
description: Successfully retrieved the notification channel.
content:
application/json:
schema:
$ref: '#/components/schemas/NotificationChannelResponse'
'400':
"$ref": "#/components/responses/bad_request"
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'404':
"$ref": "#/components/responses/not_found"
'500':
"$ref": "#/components/responses/internal_error"
put:
tags:
- Notifications
summary: Update Notification Channel
description: Updates an existing notification channel.
operationId: updateNotificationChannel
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NotificationChannelRequest'
responses:
'200':
description: Notification channel updated successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/NotificationChannelResponse'
'400':
"$ref": "#/components/responses/bad_request"
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'404':
"$ref": "#/components/responses/not_found"
'500':
"$ref": "#/components/responses/internal_error"
delete:
tags:
- Notifications
summary: Delete Notification Channel
description: Deletes a notification channel by its ID.
operationId: deleteNotificationChannel
responses:
'200':
description: Notification channel deleted successfully.
content:
application/json:
schema:
type: object
example: { }
'400':
"$ref": "#/components/responses/bad_request"
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'404':
"$ref": "#/components/responses/not_found"
'500':
"$ref": "#/components/responses/internal_error"

View File

@@ -9,6 +9,7 @@ import (
"time"
"github.com/oapi-codegen/runtime"
openapi_types "github.com/oapi-codegen/runtime/types"
)
const (
@@ -664,6 +665,24 @@ func (e NetworkResourceType) Valid() bool {
}
}
// Defines values for NotificationChannelType.
const (
NotificationChannelTypeEmail NotificationChannelType = "email"
NotificationChannelTypeWebhook NotificationChannelType = "webhook"
)
// Valid indicates whether the value is a known member of the NotificationChannelType enum.
func (e NotificationChannelType) Valid() bool {
switch e {
case NotificationChannelTypeEmail:
return true
case NotificationChannelTypeWebhook:
return true
default:
return false
}
}
// Defines values for PeerNetworkRangeCheckAction.
const (
PeerNetworkRangeCheckActionAllow PeerNetworkRangeCheckAction = "allow"
@@ -1893,6 +1912,12 @@ type EDRSentinelOneResponse struct {
UpdatedAt time.Time `json:"updated_at"`
}
// EmailTarget Target configuration for email notification channels.
type EmailTarget struct {
// Emails List of email addresses to send notifications to.
Emails []openapi_types.Email `json:"emails"`
}
// ErrorResponse Standard error response. Note: The exact structure of this error response is inferred from `util.WriteErrorResponse` and `util.WriteError` usage in the provided Go code, as a specific Go struct for errors was not provided.
type ErrorResponse struct {
// Message A human-readable error message.
@@ -2666,6 +2691,67 @@ type NetworkTrafficUser struct {
Name string `json:"name"`
}
// NotificationChannelRequest Request body for creating or updating a notification channel.
type NotificationChannelRequest struct {
// Enabled Whether this notification channel is active.
Enabled bool `json:"enabled"`
// EventTypes List of activity event type codes this channel subscribes to.
EventTypes []NotificationEventType `json:"event_types"`
// Target Channel-specific target configuration. The shape depends on the `type` field:
// - `email`: requires an `EmailTarget` object
// - `webhook`: requires a `WebhookTarget` object
Target *NotificationChannelRequest_Target `json:"target,omitempty"`
// Type The type of notification channel.
Type NotificationChannelType `json:"type"`
}
// NotificationChannelRequest_Target Channel-specific target configuration. The shape depends on the `type` field:
// - `email`: requires an `EmailTarget` object
// - `webhook`: requires a `WebhookTarget` object
type NotificationChannelRequest_Target struct {
union json.RawMessage
}
// NotificationChannelResponse A notification channel configuration.
type NotificationChannelResponse struct {
// Enabled Whether this notification channel is active.
Enabled bool `json:"enabled"`
// EventTypes List of activity event type codes this channel subscribes to.
EventTypes []NotificationEventType `json:"event_types"`
// Id Unique identifier of the notification channel.
Id *string `json:"id,omitempty"`
// Target Channel-specific target configuration. The shape depends on the `type` field:
// - `email`: an `EmailTarget` object
// - `webhook`: a `WebhookTarget` object
Target *NotificationChannelResponse_Target `json:"target,omitempty"`
// Type The type of notification channel.
Type NotificationChannelType `json:"type"`
}
// NotificationChannelResponse_Target Channel-specific target configuration. The shape depends on the `type` field:
// - `email`: an `EmailTarget` object
// - `webhook`: a `WebhookTarget` object
type NotificationChannelResponse_Target struct {
union json.RawMessage
}
// NotificationChannelType The type of notification channel.
type NotificationChannelType string
// NotificationEventType An activity event type code. See `GET /api/integrations/notifications/types` for the full list
// of supported event types and their human-readable descriptions.
type NotificationEventType = string
// NotificationTypeEntry A map of event type codes to their human-readable descriptions.
type NotificationTypeEntry map[string]string
// OSVersionCheck Posture check for the version of operating system
type OSVersionCheck struct {
// Android Posture check for the version of operating system
@@ -4211,6 +4297,16 @@ type UserRequest struct {
Role string `json:"role"`
}
// WebhookTarget Target configuration for webhook notification channels.
type WebhookTarget struct {
// Headers Custom HTTP headers sent with each webhook request.
// Values are write-only; in GET responses all values are masked.
Headers *map[string]string `json:"headers,omitempty"`
// Url The webhook endpoint URL to send notifications to.
Url string `json:"url"`
}
// WorkloadRequest defines model for WorkloadRequest.
type WorkloadRequest struct {
union json.RawMessage
@@ -4564,6 +4660,12 @@ type PostApiIntegrationsMspTenantsIdSubscriptionJSONRequestBody PostApiIntegrati
// PostApiIntegrationsMspTenantsIdUnlinkJSONRequestBody defines body for PostApiIntegrationsMspTenantsIdUnlink for application/json ContentType.
type PostApiIntegrationsMspTenantsIdUnlinkJSONRequestBody PostApiIntegrationsMspTenantsIdUnlinkJSONBody
// CreateNotificationChannelJSONRequestBody defines body for CreateNotificationChannel for application/json ContentType.
type CreateNotificationChannelJSONRequestBody = NotificationChannelRequest
// UpdateNotificationChannelJSONRequestBody defines body for UpdateNotificationChannel for application/json ContentType.
type UpdateNotificationChannelJSONRequestBody = NotificationChannelRequest
// CreateSCIMIntegrationJSONRequestBody defines body for CreateSCIMIntegration for application/json ContentType.
type CreateSCIMIntegrationJSONRequestBody = CreateScimIntegrationRequest
@@ -4660,6 +4762,130 @@ type PutApiUsersUserIdPasswordJSONRequestBody = PasswordChangeRequest
// PostApiUsersUserIdTokensJSONRequestBody defines body for PostApiUsersUserIdTokens for application/json ContentType.
type PostApiUsersUserIdTokensJSONRequestBody = PersonalAccessTokenRequest
// AsEmailTarget returns the union data inside the NotificationChannelRequest_Target as a EmailTarget
func (t NotificationChannelRequest_Target) AsEmailTarget() (EmailTarget, error) {
var body EmailTarget
err := json.Unmarshal(t.union, &body)
return body, err
}
// FromEmailTarget overwrites any union data inside the NotificationChannelRequest_Target as the provided EmailTarget
func (t *NotificationChannelRequest_Target) FromEmailTarget(v EmailTarget) error {
b, err := json.Marshal(v)
t.union = b
return err
}
// MergeEmailTarget performs a merge with any union data inside the NotificationChannelRequest_Target, using the provided EmailTarget
func (t *NotificationChannelRequest_Target) MergeEmailTarget(v EmailTarget) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JSONMerge(t.union, b)
t.union = merged
return err
}
// AsWebhookTarget returns the union data inside the NotificationChannelRequest_Target as a WebhookTarget
func (t NotificationChannelRequest_Target) AsWebhookTarget() (WebhookTarget, error) {
var body WebhookTarget
err := json.Unmarshal(t.union, &body)
return body, err
}
// FromWebhookTarget overwrites any union data inside the NotificationChannelRequest_Target as the provided WebhookTarget
func (t *NotificationChannelRequest_Target) FromWebhookTarget(v WebhookTarget) error {
b, err := json.Marshal(v)
t.union = b
return err
}
// MergeWebhookTarget performs a merge with any union data inside the NotificationChannelRequest_Target, using the provided WebhookTarget
func (t *NotificationChannelRequest_Target) MergeWebhookTarget(v WebhookTarget) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JSONMerge(t.union, b)
t.union = merged
return err
}
func (t NotificationChannelRequest_Target) MarshalJSON() ([]byte, error) {
b, err := t.union.MarshalJSON()
return b, err
}
func (t *NotificationChannelRequest_Target) UnmarshalJSON(b []byte) error {
err := t.union.UnmarshalJSON(b)
return err
}
// AsEmailTarget returns the union data inside the NotificationChannelResponse_Target as a EmailTarget
func (t NotificationChannelResponse_Target) AsEmailTarget() (EmailTarget, error) {
var body EmailTarget
err := json.Unmarshal(t.union, &body)
return body, err
}
// FromEmailTarget overwrites any union data inside the NotificationChannelResponse_Target as the provided EmailTarget
func (t *NotificationChannelResponse_Target) FromEmailTarget(v EmailTarget) error {
b, err := json.Marshal(v)
t.union = b
return err
}
// MergeEmailTarget performs a merge with any union data inside the NotificationChannelResponse_Target, using the provided EmailTarget
func (t *NotificationChannelResponse_Target) MergeEmailTarget(v EmailTarget) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JSONMerge(t.union, b)
t.union = merged
return err
}
// AsWebhookTarget returns the union data inside the NotificationChannelResponse_Target as a WebhookTarget
func (t NotificationChannelResponse_Target) AsWebhookTarget() (WebhookTarget, error) {
var body WebhookTarget
err := json.Unmarshal(t.union, &body)
return body, err
}
// FromWebhookTarget overwrites any union data inside the NotificationChannelResponse_Target as the provided WebhookTarget
func (t *NotificationChannelResponse_Target) FromWebhookTarget(v WebhookTarget) error {
b, err := json.Marshal(v)
t.union = b
return err
}
// MergeWebhookTarget performs a merge with any union data inside the NotificationChannelResponse_Target, using the provided WebhookTarget
func (t *NotificationChannelResponse_Target) MergeWebhookTarget(v WebhookTarget) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JSONMerge(t.union, b)
t.union = merged
return err
}
func (t NotificationChannelResponse_Target) MarshalJSON() ([]byte, error) {
b, err := t.union.MarshalJSON()
return b, err
}
func (t *NotificationChannelResponse_Target) UnmarshalJSON(b []byte) error {
err := t.union.UnmarshalJSON(b)
return err
}
// AsBundleWorkloadRequest returns the union data inside the WorkloadRequest as a BundleWorkloadRequest
func (t WorkloadRequest) AsBundleWorkloadRequest() (BundleWorkloadRequest, error) {
var body BundleWorkloadRequest