From 43fb7aa0be85307db0537ac132c3dbebe8da3ea5 Mon Sep 17 00:00:00 2001
From: Yu-Hsin Yang <cindy02017@gmail.com>
Date: Mon, 3 Jun 2024 21:46:02 +0200
Subject: [PATCH] Beautify webhook messages

---
 alert/alert.go | 95 ++++++++++++++++++++++++++++++++++----------------
 1 file changed, 64 insertions(+), 31 deletions(-)

diff --git a/alert/alert.go b/alert/alert.go
index b748b04..f8ad6f7 100644
--- a/alert/alert.go
+++ b/alert/alert.go
@@ -13,9 +13,21 @@ import (
 	"time"
 )
 
+type Field struct {
+    Title string `json:"title"`
+    Value string `json:"value"`
+    Short bool   `json:"short"`
+}
+
+type Attachment struct {
+    Color  string  `json:"color"`
+    Fields []Field `json:"fields"`
+}
+
 // Payload struct to hold the JSON structure for our message
 type Payload struct {
 	Text string `json:"text"`
+	Attachments []Attachment `json:"attachments,omitempty"`
 }
 
 type Config struct {
@@ -33,11 +45,11 @@ type Canary struct {
 
 type HTTPCanary struct {
 	Canary
+	UserAgent  string
 	RemoteIP   string
 	RemotePort uint16
 	LocalIP    string
 	LocalPort  uint16
-	UserAgent  string
 	FullUrl    string
 	Referer    string
 }
@@ -69,15 +81,14 @@ func Initialize(slackhook string, silenceSeconds uint16, doSyslog bool) {
 }
 
 // Send a slack message
-func PostSlackHook(message string) {
+func PostSlackHook(message string, attachments []Attachment) {
 	if time.Since(lastNotifTime).Seconds() < float64(config.silenceSeconds) {
 		lastNotifTime = time.Now()
 		return
 	}
 	lastNotifTime = time.Now()
-
-	toSend := fmt.Sprintf("TokenAlert `%s`", message)
-	payload := Payload{Text: toSend}
+	
+	payload := Payload{Text: message, Attachments: attachments}
 
 	// Marshal the payload into JSON
 	jsonData, err := json.Marshal(payload)
@@ -110,35 +121,62 @@ func PostSlackHook(message string) {
 }
 
 // Format the canary alert message
-func formatSlackMessage(canary interface{}) string {
+func formatSlackMessage(canary interface{}, remoteIP string) (string, []Attachment) {
 	v := reflect.ValueOf(canary)
 	t := v.Type()
 
-	var message strings.Builder
-	message.WriteString("*Canary Alert*\n\n")
-
-	for i := 0; i < v.NumField(); i++ {
-		field := v.Field(i)
-		typeField := t.Field(i)
-		if typeField.Name == "Canary" {
-			for j := 0; j < field.NumField(); j++ {
-				subField := field.Field(j)
-				subTypeField := field.Type().Field(j)
-				message.WriteString(fmt.Sprintf("*%s*: `%v`\n", subTypeField.Name, subField.Interface()))
-			}
-		} else {
-			message.WriteString(fmt.Sprintf("*%s*: `%v`\n", typeField.Name, field.Interface()))
-		}
+	// Choose color depending on the alert level
+	var color string
+	switch v.FieldByName("Level").Interface() {
+	case "low":
+		color = "#36a64f"
+	case "medium":
+		color = "#ffcc00"
+	case "high":
+		color = "#ff0000"
+	default:
+		color = "#000000"
 	}
-	return message.String()
+
+
+	var message strings.Builder
+
+	message.WriteString("**Canary Alert**")
+
+    var fields []Field
+    for i := 0; i < v.NumField(); i++ {
+        field := v.Field(i)
+        typeField := t.Field(i)
+        if typeField.Name == "Canary" {
+            for j := 0; j < field.NumField(); j++ {
+                subField := field.Field(j)
+                subTypeField := field.Type().Field(j)
+                fields = append(fields, Field{Title: subTypeField.Name, Value: fmt.Sprintf("%v", subField.Interface()), Short: true})
+            }
+        } else {
+            fields = append(fields, Field{Title: typeField.Name, Value: fmt.Sprintf("%v", field.Interface()), Short: true})
+        }
+    }
+
+    // Append the ipinfo URL
+    fields = append(fields, Field{Title: "CheckRemoteIPInfo", Value: fmt.Sprintf("http://ipinfo.io/%s", remoteIP)})
+
+	attachments := []Attachment{
+        {
+            Color:  color,
+            Fields: fields,
+        },
+    }
+
+    return message.String(), attachments
 }
 
-func (h HTTPCanary) FormatSlackMessage() string {
-	return formatSlackMessage(h)
+func (h HTTPCanary) FormatSlackMessage() (string, []Attachment){
+	return formatSlackMessage(h, h.RemoteIP)
 }
 
-func (d DNSCanary) FormatSlackMessage() string {
-	return formatSlackMessage(d)
+func (d DNSCanary) FormatSlackMessage() (string, []Attachment) {
+	return formatSlackMessage(d, d.RemoteIP)
 }
 
 // Handler for HTTP canary alerts
@@ -164,9 +202,6 @@ func HTTPAlert(canaryinfo HTTPCanary, alertType string) {
 	}
 
 	if alertType == "slack" || alertType == "all" {
-		//Slack alert if configured
-		// PostSlackHook(fmt.Sprintf("``` %+v ```", canaryinfo))
-		
 		// Slack alert if configured
 		PostSlackHook(canaryinfo.FormatSlackMessage())
 	}
@@ -192,8 +227,6 @@ func DNSAlert(canaryinfo DNSCanary, alertType string) {
 	}
 
 	if alertType == "slack" || alertType == "all" {
-		//Slack alert if configured
-		// PostSlackHook(fmt.Sprintf("``` %+v ```", canaryinfo))
 		// Slack alert if configured
 		PostSlackHook(canaryinfo.FormatSlackMessage())
 	}
-- 
GitLab