feat: register email as feature-flagged notification service

Add email as a recognized, feature-flagged notification service type.
The flag defaults to false and acts as a dispatch gate alongside the
existing discord, gotify, and webhook notification service flags.

- Add FlagEmailServiceEnabled constant to the notifications feature flag
  registry with the canonical key convention
- Register the flag in the handler defaults so it appears in the feature
  flags API response with a false default
- Recognise 'email' as a supported notification provider type so that
  providers of this type pass the type validation gate
- Gate email dispatch on the new flag in isDispatchEnabled() following
  the same pattern as gotify and webhook service flags
- Expand the E2E test fixtures FeatureFlags interface to include the new
  flag key so typed fixture objects remain accurate

No email message dispatch is wired in this commit; the flag registration
alone makes the email provider type valid and toggleable.
This commit is contained in:
GitHub Actions
2026-03-05 03:36:27 +00:00
parent 8ea907066b
commit 5a58404e1b
6 changed files with 38 additions and 5 deletions

View File

@@ -30,6 +30,7 @@ var defaultFlags = []string{
"feature.crowdsec.console_enrollment",
"feature.notifications.engine.notify_v1.enabled",
"feature.notifications.service.discord.enabled",
"feature.notifications.service.email.enabled",
"feature.notifications.service.gotify.enabled",
"feature.notifications.service.webhook.enabled",
"feature.notifications.security_provider_events.enabled", // Blocker 3: Add security_provider_events gate
@@ -41,6 +42,7 @@ var defaultFlagValues = map[string]bool{
"feature.crowdsec.console_enrollment": false,
"feature.notifications.engine.notify_v1.enabled": false,
"feature.notifications.service.discord.enabled": false,
"feature.notifications.service.email.enabled": false,
"feature.notifications.service.gotify.enabled": false,
"feature.notifications.service.webhook.enabled": false,
"feature.notifications.security_provider_events.enabled": false, // Blocker 3: Default disabled for this stage

View File

@@ -14,10 +14,10 @@ type NotificationConfig struct {
MinLogLevel string `json:"min_log_level"` // error, warn, info, debug
WebhookURL string `json:"webhook_url"`
// Blocker 2 Fix: API surface uses security_* field names per spec (internal fields remain notify_*)
NotifyWAFBlocks bool `json:"security_waf_enabled"`
NotifyACLDenies bool `json:"security_acl_enabled"`
NotifyRateLimitHits bool `json:"security_rate_limit_enabled"`
NotifyCrowdSecDecisions bool `json:"security_crowdsec_enabled"`
NotifyWAFBlocks bool `json:"security_waf_enabled"`
NotifyACLDenies bool `json:"security_acl_enabled"`
NotifyRateLimitHits bool `json:"security_rate_limit_enabled"`
NotifyCrowdSecDecisions bool `json:"security_crowdsec_enabled"`
// Legacy destination fields (compatibility, not stored in DB)
DiscordWebhookURL string `gorm:"-" json:"discord_webhook_url,omitempty"`

View File

@@ -3,6 +3,7 @@ package notifications
const (
FlagNotifyEngineEnabled = "feature.notifications.engine.notify_v1.enabled"
FlagDiscordServiceEnabled = "feature.notifications.service.discord.enabled"
FlagEmailServiceEnabled = "feature.notifications.service.email.enabled"
FlagGotifyServiceEnabled = "feature.notifications.service.gotify.enabled"
FlagWebhookServiceEnabled = "feature.notifications.service.webhook.enabled"
FlagSecurityProviderEventsEnabled = "feature.notifications.security_provider_events.enabled"

View File

@@ -105,7 +105,7 @@ func supportsJSONTemplates(providerType string) bool {
func isSupportedNotificationProviderType(providerType string) bool {
switch strings.ToLower(strings.TrimSpace(providerType)) {
case "discord", "gotify", "webhook":
case "discord", "email", "gotify", "webhook":
return true
default:
return false
@@ -116,6 +116,8 @@ func (s *NotificationService) isDispatchEnabled(providerType string) bool {
switch strings.ToLower(strings.TrimSpace(providerType)) {
case "discord":
return true
case "email":
return s.getFeatureFlagValue(notifications.FlagEmailServiceEnabled, false)
case "gotify":
return s.getFeatureFlagValue(notifications.FlagGotifyServiceEnabled, true)
case "webhook":

View File

@@ -15,6 +15,7 @@ import (
"time"
"github.com/Wikid82/charon/backend/internal/models"
"github.com/Wikid82/charon/backend/internal/notifications"
"github.com/Wikid82/charon/backend/internal/security"
"github.com/Wikid82/charon/backend/internal/trace"
"github.com/stretchr/testify/assert"
@@ -2316,6 +2317,30 @@ func TestIsDispatchEnabled_WebhookDefaultTrue(t *testing.T) {
assert.True(t, svc.isDispatchEnabled("webhook"))
}
func TestFlagEmailServiceEnabled_ConstantValue(t *testing.T) {
assert.Equal(t, "feature.notifications.service.email.enabled", notifications.FlagEmailServiceEnabled)
}
func TestIsSupportedNotificationProviderType_Email(t *testing.T) {
assert.True(t, isSupportedNotificationProviderType("email"))
}
func TestIsDispatchEnabled_EmailDefaultFalse(t *testing.T) {
db := setupNotificationTestDB(t)
_ = db.AutoMigrate(&models.Setting{})
svc := NewNotificationService(db)
// No feature flag row — email defaults to false
assert.False(t, svc.isDispatchEnabled("email"))
// Explicitly set flag to true — should now return true
require.NoError(t, db.Create(&models.Setting{
Key: notifications.FlagEmailServiceEnabled,
Value: "true",
}).Error)
assert.True(t, svc.isDispatchEnabled("email"))
}
func TestTestProvider_GotifyWorksWithoutFeatureFlag(t *testing.T) {
db := setupNotificationTestDB(t)
_ = db.AutoMigrate(&models.Setting{})

View File

@@ -272,6 +272,7 @@ export const certificateEmailSettings = {
export interface FeatureFlags {
cerberus_enabled: boolean;
crowdsec_console_enrollment: boolean;
"feature.notifications.service.email.enabled": boolean;
uptime_monitoring: boolean;
}
@@ -281,6 +282,7 @@ export interface FeatureFlags {
export const defaultFeatureFlags: FeatureFlags = {
cerberus_enabled: false,
crowdsec_console_enrollment: false,
"feature.notifications.service.email.enabled": false,
uptime_monitoring: false,
};
@@ -290,6 +292,7 @@ export const defaultFeatureFlags: FeatureFlags = {
export const allFeaturesEnabled: FeatureFlags = {
cerberus_enabled: true,
crowdsec_console_enrollment: true,
"feature.notifications.service.email.enabled": false,
uptime_monitoring: true,
};