149 lines
6.9 KiB
Go
149 lines
6.9 KiB
Go
package notify
|
|
|
|
import "encoding/json"
|
|
import "fmt"
|
|
import "strconv"
|
|
import "strings"
|
|
|
|
import "codit/internal/models"
|
|
|
|
// transportSchemas is the source of truth for each transport type's configurable
|
|
// fields. The admin UI renders a real input per field (so no raw JSON is typed),
|
|
// and Create/Update validate + split submitted values against it. Adding a new
|
|
// transport = add a schema here plus its delivery impl; the UI adapts.
|
|
var transportSchemas = []models.TransportSchema{
|
|
{
|
|
Type: models.TransportTypeWebhook,
|
|
Label: "Webhook",
|
|
AddressLabel: "Webhook URL",
|
|
AddressHelp: "The endpoint the event JSON is POSTed to (per channel).",
|
|
Fields: []models.TransportField{
|
|
{Key: "method", Label: "HTTP Method", Kind: models.TransportFieldSelect, Default: "POST", Options: []models.TransportFieldOption{{Value: "POST", Label: "POST"}, {Value: "PUT", Label: "PUT"}}},
|
|
{Key: "header_name", Label: "Auth Header Name", Kind: models.TransportFieldString, Placeholder: "Authorization", Help: "Header the auth token is sent in (defaults to Authorization)."},
|
|
{Key: "token", Label: "Auth Token", Kind: models.TransportFieldSecret, Help: "Sent as the value of the auth header, e.g. \"Bearer abc123\". Optional."},
|
|
{Key: "ca_ref", Label: "Verify with CA", Kind: models.TransportFieldSelect, OptionsSource: models.TransportOptionSourcePKICAs, Help: "For HTTPS endpoints signed by an internal CA. Trusted in addition to the system roots. Leave unset to use system roots only."},
|
|
{Key: "insecure_skip_verify", Label: "Ignore TLS certificate errors", Kind: models.TransportFieldBool, Help: "Skip server certificate verification entirely. Use only for a self-signed endpoint you control."},
|
|
},
|
|
},
|
|
{
|
|
Type: models.TransportTypeSMTP,
|
|
Label: "Email (SMTP)",
|
|
AddressLabel: "Recipient(s)",
|
|
AddressHelp: "One or more destination email addresses, comma-separated.",
|
|
Fields: []models.TransportField{
|
|
{Key: "host", Label: "SMTP Host", Kind: models.TransportFieldString, Required: true, Placeholder: "smtp.example.com"},
|
|
{Key: "port", Label: "Port", Kind: models.TransportFieldInt, Default: "587"},
|
|
{Key: "security", Label: "Security", Kind: models.TransportFieldSelect, Default: "starttls", Options: []models.TransportFieldOption{{Value: "starttls", Label: "STARTTLS"}, {Value: "smtps", Label: "TLS (SMTPS)"}, {Value: "none", Label: "None (plaintext)"}}},
|
|
{Key: "ca_ref", Label: "Verify with CA", Kind: models.TransportFieldSelect, OptionsSource: models.TransportOptionSourcePKICAs, Help: "For a mail server whose certificate is signed by an internal CA (STARTTLS/SMTPS). Trusted in addition to system roots; ignored when Security is None."},
|
|
{Key: "insecure_skip_verify", Label: "Ignore TLS certificate errors", Kind: models.TransportFieldBool, Help: "Skip server certificate verification (STARTTLS/SMTPS). Leave off unless using a self-signed cert."},
|
|
{Key: "from", Label: "From Address", Kind: models.TransportFieldString, Required: true, Placeholder: "alerts@example.com"},
|
|
{Key: "username", Label: "Username", Kind: models.TransportFieldString, Help: "Leave blank if the server needs no authentication."},
|
|
{Key: "password", Label: "Password", Kind: models.TransportFieldSecret},
|
|
},
|
|
},
|
|
{
|
|
Type: models.TransportTypeMSGraph,
|
|
Label: "Email (Microsoft Graph)",
|
|
AddressLabel: "Recipient(s)",
|
|
AddressHelp: "One or more destination email addresses, comma-separated.",
|
|
Fields: []models.TransportField{
|
|
{Key: "tenant_id", Label: "Tenant ID", Kind: models.TransportFieldString, Required: true},
|
|
{Key: "client_id", Label: "Client ID", Kind: models.TransportFieldString, Required: true},
|
|
{Key: "sender", Label: "Sender (mailbox)", Kind: models.TransportFieldString, Required: true, Placeholder: "alerts@example.com", Help: "The mailbox the message is sent as (sendMail on this user)."},
|
|
{Key: "cloud", Label: "Cloud", Kind: models.TransportFieldSelect, Default: "commercial", Options: []models.TransportFieldOption{{Value: "commercial", Label: "Commercial (global)"}, {Value: "gcc_high", Label: "US Gov (GCC High / DoD)"}, {Value: "china", Label: "China (21Vianet)"}}, Help: "Sovereign cloud endpoints. Use Commercial unless your tenant is in a national cloud."},
|
|
{Key: "client_secret", Label: "Client Secret", Kind: models.TransportFieldSecret, Required: true},
|
|
},
|
|
},
|
|
{
|
|
Type: models.TransportTypeMSTeams,
|
|
Label: "Microsoft Teams",
|
|
AddressLabel: "Incoming Webhook URL",
|
|
AddressHelp: "The Teams channel incoming-webhook URL (per channel). Self-contained; no provider config needed.",
|
|
Fields: []models.TransportField{},
|
|
},
|
|
{
|
|
Type: models.TransportTypeSlack,
|
|
Label: "Slack",
|
|
AddressLabel: "Incoming Webhook URL",
|
|
AddressHelp: "The Slack incoming-webhook URL (per channel). Self-contained; no provider config needed.",
|
|
Fields: []models.TransportField{},
|
|
},
|
|
}
|
|
|
|
// TransportSchemas returns a copy of all known transport type schemas.
|
|
func TransportSchemas() []models.TransportSchema {
|
|
return append([]models.TransportSchema(nil), transportSchemas...)
|
|
}
|
|
|
|
// SchemaFor returns the schema for a transport type.
|
|
func SchemaFor(t string) (models.TransportSchema, bool) {
|
|
var s models.TransportSchema
|
|
for _, s = range transportSchemas {
|
|
if s.Type == t {
|
|
return s, true
|
|
}
|
|
}
|
|
return models.TransportSchema{}, false
|
|
}
|
|
|
|
// SplitTransportValues validates flat string inputs against a type's schema and
|
|
// returns the typed non-secret config as JSON plus the secret field values that
|
|
// were provided (non-empty). Required non-secret fields must be present; secret
|
|
// requiredness is enforced by the caller on create (an update may omit a secret
|
|
// to keep the stored one).
|
|
func SplitTransportValues(schemaType string, configValues map[string]string, secretValues map[string]string) (string, map[string]string, error) {
|
|
var schema models.TransportSchema
|
|
var ok bool
|
|
var cfg map[string]any
|
|
var secrets map[string]string
|
|
var f models.TransportField
|
|
var raw string
|
|
var out []byte
|
|
var err error
|
|
|
|
schema, ok = SchemaFor(schemaType)
|
|
if !ok {
|
|
return "", nil, fmt.Errorf("unknown transport type %q", schemaType)
|
|
}
|
|
cfg = map[string]any{}
|
|
secrets = map[string]string{}
|
|
for _, f = range schema.Fields {
|
|
if f.Kind == models.TransportFieldSecret {
|
|
raw = strings.TrimSpace(secretValues[f.Key])
|
|
if raw != "" {
|
|
secrets[f.Key] = raw
|
|
}
|
|
continue
|
|
}
|
|
raw = strings.TrimSpace(configValues[f.Key])
|
|
if raw == "" {
|
|
raw = f.Default
|
|
}
|
|
if raw == "" && f.Required {
|
|
return "", nil, fmt.Errorf("%s is required", f.Label)
|
|
}
|
|
switch f.Kind {
|
|
case models.TransportFieldInt:
|
|
var n int64
|
|
if raw == "" {
|
|
cfg[f.Key] = 0
|
|
} else {
|
|
n, err = strconv.ParseInt(raw, 10, 64)
|
|
if err != nil {
|
|
return "", nil, fmt.Errorf("%s must be a number", f.Label)
|
|
}
|
|
cfg[f.Key] = n
|
|
}
|
|
case models.TransportFieldBool:
|
|
cfg[f.Key] = raw == "true" || raw == "1"
|
|
default:
|
|
cfg[f.Key] = raw
|
|
}
|
|
}
|
|
out, err = json.Marshal(cfg)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
return string(out), secrets, nil
|
|
}
|