From 6a6f954b67f4bfe7f33a31a4307f7f19b7037383 Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Wed, 9 Jul 2025 19:48:07 +0200 Subject: [PATCH 1/9] split middlewares into seperate files --- internals/proxy/middlewares/auth.go | 112 ++++++++ internals/proxy/middlewares/endpoints.go | 30 ++ internals/proxy/middlewares/template.go | 158 +++++++++++ internals/proxy/proxy.go | 335 ----------------------- main.go | 29 +- utils/query/query.go | 82 ++++++ 6 files changed, 401 insertions(+), 345 deletions(-) create mode 100644 internals/proxy/middlewares/auth.go create mode 100644 internals/proxy/middlewares/endpoints.go create mode 100644 internals/proxy/middlewares/template.go create mode 100644 utils/query/query.go diff --git a/internals/proxy/middlewares/auth.go b/internals/proxy/middlewares/auth.go new file mode 100644 index 0000000..bafebdf --- /dev/null +++ b/internals/proxy/middlewares/auth.go @@ -0,0 +1,112 @@ +package middlewares + +import ( + "encoding/base64" + "net/http" + "net/url" + "strings" + + log "github.com/codeshelldev/secured-signal-api/utils/logger" +) + +type AuthMiddleware struct { + Next http.Handler + Token string +} + +type authType string + +const ( + Bearer authType = "Bearer" + Basic authType = "Basic" + Query authType = "Query" + None authType = "None" +) + +func getAuthType(str string) authType { + switch str { + case "Bearer": + return Bearer + case "Basic": + return Basic + default: + return None + } +} + +func (data AuthMiddleware) Use() http.Handler { + next := data.Next + token := data.Token + + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if token == "" { + next.ServeHTTP(w, req) + return + } + + log.Info("Request:", req.Method, req.URL.Path) + + authHeader := req.Header.Get("Authorization") + + authQuery := req.URL.Query().Get("@authorization") + + var authType authType = None + + success := false + + if authHeader != "" { + authBody := strings.Split(authHeader, " ") + + authType = getAuthType(authBody[0]) + authToken := authBody[1] + + switch authType { + case Bearer: + if authToken == token { + success = true + } + + case Basic: + basicAuthBody, err := base64.StdEncoding.DecodeString(authToken) + + if err != nil { + log.Error("Could not decode Basic Auth Payload: ", err.Error()) + } + + basicAuth := string(basicAuthBody) + basicAuthParams := strings.Split(basicAuth, ":") + + user := "api" + + if basicAuthParams[0] == user && basicAuthParams[1] == token { + success = true + } + } + + } else if authQuery != "" { + authType = Query + + authToken, _ := url.QueryUnescape(authQuery) + + if authToken == token { + success = true + + modifiedQuery := req.URL.Query() + + modifiedQuery.Del("@authorization") + + req.URL.RawQuery = modifiedQuery.Encode() + } + } + + if !success { + w.Header().Set("WWW-Authenticate", "Basic realm=\"Login Required\", Bearer realm=\"Access Token Required\"") + + log.Warn("User failed ", string(authType), " Auth") + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + + next.ServeHTTP(w, req) + }) +} diff --git a/internals/proxy/middlewares/endpoints.go b/internals/proxy/middlewares/endpoints.go new file mode 100644 index 0000000..0899f2a --- /dev/null +++ b/internals/proxy/middlewares/endpoints.go @@ -0,0 +1,30 @@ +package middlewares + +import ( + "net/http" + "slices" + + log "github.com/codeshelldev/secured-signal-api/utils/logger" +) + +type EndpointsMiddleware struct { + Next http.Handler + BlockedEndpoints []string +} + +func (data EndpointsMiddleware) Use() http.Handler { + next := data.Next + BLOCKED_ENDPOINTS := data.BlockedEndpoints + + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + reqPath := req.URL.Path + + if slices.Contains(BLOCKED_ENDPOINTS, reqPath) { + log.Warn("User tried to access blocked endpoint: ", reqPath) + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + + next.ServeHTTP(w, req) + }) +} diff --git a/internals/proxy/middlewares/template.go b/internals/proxy/middlewares/template.go new file mode 100644 index 0000000..27c2d56 --- /dev/null +++ b/internals/proxy/middlewares/template.go @@ -0,0 +1,158 @@ +package middlewares + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "net/url" + "regexp" + "strconv" + "strings" + "text/template" + + log "github.com/codeshelldev/secured-signal-api/utils/logger" + query "github.com/codeshelldev/secured-signal-api/utils/query" +) + +type TemplateMiddleware struct { + Next http.Handler + Variables map[string]interface{} +} + +func renderTemplate(name string, tmplStr string, data any) (string, error) { + tmpl, err := template.New(name).Parse(tmplStr) + + if err != nil { + return "", err + } + var buf bytes.Buffer + + err = tmpl.Execute(&buf, data) + + if err != nil { + return "", err + } + return buf.String(), nil +} + +func templateJSON(data map[string]interface{}, variables map[string]interface{}) map[string]interface{} { + for k, v := range data { + str, ok := v.(string) + + if ok { + re, err := regexp.Compile(`{{\s*\.([A-Za-z_][A-Za-z0-9_]*)\s*}}`) + + if err != nil { + log.Error("Encountered Error while Compiling Regex: ", err.Error()) + } + + matches := re.FindAllStringSubmatch(str, -1) + + if len(matches) > 1 { + for i, tmplStr := range matches { + + tmplKey := matches[i][1] + + variable, err := json.Marshal(variables[tmplKey]) + + if err != nil { + log.Error("Could not decode JSON: ", err.Error()) + break + } + + data[k] = strings.ReplaceAll(str, string(variable), tmplStr[0]) + } + } else if len(matches) == 1 { + tmplKey := matches[0][1] + + data[k] = variables[tmplKey] + } + } + } + + return data +} + +func (data TemplateMiddleware) Use() http.Handler { + next := data.Next + VARIABLES := data.Variables + + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if req.Body != nil { + bodyBytes, err := io.ReadAll(req.Body) + + if err != nil { + log.Error("Could not read Body: ", err.Error()) + http.Error(w, "Internal Error", http.StatusInternalServerError) + return + } + + req.Body.Close() + + var modifiedBodyData map[string]interface{} + + err = json.Unmarshal(bodyBytes, &modifiedBodyData) + + if err != nil { + log.Error("Could not decode Body: ", err.Error()) + http.Error(w, "Internal Error", http.StatusInternalServerError) + return + } + + modifiedBodyData = templateJSON(modifiedBodyData, VARIABLES) + + if req.URL.RawQuery != "" { + decodedQuery, _ := url.QueryUnescape(req.URL.RawQuery) + + log.Debug("Decoded Query: ", decodedQuery) + + templatedQuery, _ := renderTemplate("query", decodedQuery, VARIABLES) + + modifiedQuery := req.URL.Query() + + queryData := query.ParseRawQuery(templatedQuery) + + for key, value := range queryData { + keyWithoutPrefix, found := strings.CutPrefix(key, "@") + + if found { + modifiedBodyData[keyWithoutPrefix] = query.ParseTypedQuery(value) + + modifiedQuery.Del(key) + } + } + + req.URL.RawQuery = modifiedQuery.Encode() + + log.Debug("Applied Query Templating: ", templatedQuery) + } + + modifiedBodyBytes, err := json.Marshal(modifiedBodyData) + + if err != nil { + log.Error("Could not encode Body: ", err.Error()) + http.Error(w, "Internal Error", http.StatusInternalServerError) + return + } + + modifiedBody := string(modifiedBodyBytes) + + log.Debug("Applied Body Templating: ", modifiedBody) + + req.Body = io.NopCloser(bytes.NewReader(modifiedBodyBytes)) + + req.ContentLength = int64(len(modifiedBody)) + req.Header.Set("Content-Length", strconv.Itoa(len(modifiedBody))) + } + + reqPath := req.URL.Path + reqPath, _ = url.PathUnescape(reqPath) + + modifiedReqPath, _ := renderTemplate("path", reqPath, VARIABLES) + + req.URL.Path = modifiedReqPath + + next.ServeHTTP(w, req) + }) +} diff --git a/internals/proxy/proxy.go b/internals/proxy/proxy.go index b0dd0de..e99c01c 100644 --- a/internals/proxy/proxy.go +++ b/internals/proxy/proxy.go @@ -1,345 +1,10 @@ package proxy import ( - "bytes" - "encoding/base64" - "encoding/json" - "io" - "net/http" "net/http/httputil" "net/url" - "regexp" - "slices" - "strconv" - "strings" - "text/template" - - log "github.com/codeshelldev/secured-signal-api/utils/logger" -) - -type AuthType string - -const ( - Bearer AuthType = "Bearer" - Basic AuthType = "Basic" - Query AuthType = "Query" - None AuthType = "None" ) -func parseRawQuery(raw string) map[string][]string { - result := make(map[string][]string) - pairs := strings.SplitSeq(raw, "&") - - for pair := range pairs { - if pair == "" { - continue - } - - parts := strings.SplitN(pair, "=", 2) - - key := parts[0] - val := "" - - if len(parts) == 2 { - val = parts[1] - } - - result[key] = append(result[key], val) - } - - return result -} - -func tryParseInt(str string) (int, bool) { - isInt, err := regexp.MatchString(`^\d+$`, str) - - if err != nil { - log.Error("Encountered Error while Parsing Int", err.Error()) - } - - if isInt && err == nil { - intValue, err := strconv.Atoi(str) - - if err == nil { - return intValue, true - } - } - - return 0, false -} - -func parseTypedQuery(values []string) interface{} { - var result interface{} - - raw := values[0] - - intValue, isInt := tryParseInt(raw) - - if strings.Contains(raw, ",") || (strings.Contains(raw, "[") && strings.Contains(raw, "]")) { - if strings.Contains(raw, "[") && strings.Contains(raw, "]") { - escapedStr := strings.ReplaceAll(raw, "[", "") - escapedStr = strings.ReplaceAll(escapedStr, "]", "") - raw = escapedStr - } - - parts := strings.Split(raw, ",") - - var list []interface{} - - for _, part := range parts { - _intValue, _isInt := tryParseInt(part) - - if _isInt { - list = append(list, _intValue) - } else { - list = append(list, part) - } - } - result = list - } else if isInt { - result = intValue - } else { - result = raw - } - - return result -} - -func getAuthType(str string) AuthType { - switch str { - case "Bearer": - return Bearer - case "Basic": - return Basic - default: - return None - } -} - -func renderTemplate(name string, tmplStr string, data any) (string, error) { - tmpl, err := template.New(name).Parse(tmplStr) - - // TODO: Escape Arrays inside of strings "{{ .ARRAY }}" => [ 1, 2, 3 ] - - if err != nil { - return "", err - } - var buf bytes.Buffer - - err = tmpl.Execute(&buf, data) - - if err != nil { - return "", err - } - return buf.String(), nil -} - -func templateJSON(data map[string]interface{}, variables map[string]interface{}) map[string]interface{} { - for k, v := range data { - str, ok := v.(string) - - if ok { - re, err := regexp.Compile(`{{\s*\.([A-Za-z_][A-Za-z0-9_]*)\s*}}`) - - if err != nil { - log.Error("Encountered Error while Compiling Regex: ", err.Error()) - } - - matches := re.FindAllStringSubmatch(str, -1) - - if len(matches) > 1 { - for i, tmplStr := range matches { - - tmplKey := matches[i][1] - - variable, err := json.Marshal(variables[tmplKey]) - - if err != nil { - log.Error("Could not decode JSON: ", err.Error()) - break - } - - data[k] = strings.ReplaceAll(str, string(variable), tmplStr[0]) - } - } else if len(matches) == 1 { - tmplKey := matches[0][1] - - data[k] = variables[tmplKey] - } - } - } - - return data -} - -func AuthMiddleware(next http.Handler, token string) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if token == "" { - next.ServeHTTP(w, req) - return - } - - log.Info("Request:", req.Method, req.URL.Path) - - authHeader := req.Header.Get("Authorization") - - authQuery := req.URL.Query().Get("@authorization") - - var authType AuthType = None - - success := false - - if authHeader != "" { - authBody := strings.Split(authHeader, " ") - - authType = getAuthType(authBody[0]) - authToken := authBody[1] - - switch authType { - case Bearer: - if authToken == token { - success = true - } - - case Basic: - basicAuthBody, err := base64.StdEncoding.DecodeString(authToken) - - if err != nil { - log.Error("Could not decode Basic Auth Payload: ", err.Error()) - } - - basicAuth := string(basicAuthBody) - basicAuthParams := strings.Split(basicAuth, ":") - - user := "api" - - if basicAuthParams[0] == user && basicAuthParams[1] == token { - success = true - } - } - - } else if authQuery != "" { - authType = Query - - authToken, _ := url.QueryUnescape(authQuery) - - if authToken == token { - success = true - - modifiedQuery := req.URL.Query() - - modifiedQuery.Del("@authorization") - - req.URL.RawQuery = modifiedQuery.Encode() - } - } - - if !success { - w.Header().Set("WWW-Authenticate", "Basic realm=\"Login Required\", Bearer realm=\"Access Token Required\"") - - log.Warn("User failed ", string(authType), " Auth") - http.Error(w, "Unauthorized", http.StatusUnauthorized) - return - } - - next.ServeHTTP(w, req) - }) -} - -func BlockedEndpointMiddleware(next http.Handler, BLOCKED_ENDPOINTS []string) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - reqPath := req.URL.Path - - if slices.Contains(BLOCKED_ENDPOINTS, reqPath) { - log.Warn("User tried to access blocked endpoint: ", reqPath) - http.Error(w, "Unauthorized", http.StatusUnauthorized) - return - } - - next.ServeHTTP(w, req) - }) -} - -func TemplatingMiddleware(next http.Handler, VARIABLES map[string]interface{}) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if req.Body != nil { - bodyBytes, err := io.ReadAll(req.Body) - - if err != nil { - log.Error("Could not read Body: ", err.Error()) - http.Error(w, "Internal Error", http.StatusInternalServerError) - return - } - - req.Body.Close() - - var modifiedBodyData map[string]interface{} - - err = json.Unmarshal(bodyBytes, &modifiedBodyData) - - if err != nil { - log.Error("Could not decode Body: ", err.Error()) - http.Error(w, "Internal Error", http.StatusInternalServerError) - return - } - - modifiedBodyData = templateJSON(modifiedBodyData, VARIABLES) - - if req.URL.RawQuery != "" { - decodedQuery, _ := url.QueryUnescape(req.URL.RawQuery) - - log.Debug("Decoded Query: ", decodedQuery) - - query, _ := renderTemplate("query", decodedQuery, VARIABLES) - - modifiedQuery := req.URL.Query() - - queryData := parseRawQuery(query) - - for key, value := range queryData { - keyWithoutPrefix, found := strings.CutPrefix(key, "@") - - if found { - modifiedBodyData[keyWithoutPrefix] = parseTypedQuery(value) - - modifiedQuery.Del(key) - } - } - - req.URL.RawQuery = modifiedQuery.Encode() - - log.Debug("Applied Query Templating: ", query) - } - - modifiedBodyBytes, err := json.Marshal(modifiedBodyData) - - if err != nil { - log.Error("Could not encode Body: ", err.Error()) - http.Error(w, "Internal Error", http.StatusInternalServerError) - return - } - - modifiedBody := string(modifiedBodyBytes) - - log.Debug("Applied Body Templating: ", modifiedBody) - - req.Body = io.NopCloser(bytes.NewReader(modifiedBodyBytes)) - - req.ContentLength = int64(len(modifiedBody)) - req.Header.Set("Content-Length", strconv.Itoa(len(modifiedBody))) - } - - reqPath := req.URL.Path - reqPath, _ = url.PathUnescape(reqPath) - - modifiedReqPath, _ := renderTemplate("path", reqPath, VARIABLES) - - req.URL.Path = modifiedReqPath - - next.ServeHTTP(w, req) - }) -} - func Create(targetUrl string) *httputil.ReverseProxy { url, _ := url.Parse(targetUrl) diff --git a/main.go b/main.go index 60efb52..1888069 100644 --- a/main.go +++ b/main.go @@ -6,11 +6,12 @@ import ( "os" proxy "github.com/codeshelldev/secured-signal-api/internals/proxy" + . "github.com/codeshelldev/secured-signal-api/internals/proxy/middlewares" env "github.com/codeshelldev/secured-signal-api/utils/env" log "github.com/codeshelldev/secured-signal-api/utils/logger" ) -var handler *httputil.ReverseProxy +var initHandler *httputil.ReverseProxy var ENV env.ENV_ @@ -23,14 +24,22 @@ func main() { ENV = env.ENV - handler = proxy.Create(ENV.API_URL) + initHandler = proxy.Create(ENV.API_URL) - finalHandler := proxy.AuthMiddleware( - proxy.BlockedEndpointMiddleware( - proxy.TemplatingMiddleware(handler, - ENV.VARIABLES ), - ENV.BLOCKED_ENDPOINTS ), - ENV.API_TOKEN ) + temp_m3 := TemplateMiddleware{ + Next: initHandler, + Variables: ENV.VARIABLES, + } + + endp_m2 := EndpointsMiddleware{ + Next: temp_m3.Use(), + BlockedEndpoints: ENV.BLOCKED_ENDPOINTS, + } + + auth_m1 := AuthMiddleware{ + Next: endp_m2.Use(), + Token: ENV.API_TOKEN, + } log.Info("Initialized Proxy Handler") @@ -38,5 +47,5 @@ func main() { log.Info("Server Listening on ", addr) - http.ListenAndServe(addr, finalHandler) -} \ No newline at end of file + http.ListenAndServe(addr, auth_m1.Use()) +} diff --git a/utils/query/query.go b/utils/query/query.go new file mode 100644 index 0000000..789f33f --- /dev/null +++ b/utils/query/query.go @@ -0,0 +1,82 @@ +package query + +import ( + "regexp" + "strconv" + "strings" +) + +func ParseRawQuery(raw string) map[string][]string { + result := make(map[string][]string) + pairs := strings.SplitSeq(raw, "&") + + for pair := range pairs { + if pair == "" { + continue + } + + parts := strings.SplitN(pair, "=", 2) + + key := parts[0] + val := "" + + if len(parts) == 2 { + val = parts[1] + } + + result[key] = append(result[key], val) + } + + return result +} + +func TryParseInt(str string) (int, bool) { + isInt, err := regexp.MatchString(`^\d+$`, str) + + if isInt && err == nil { + intValue, err := strconv.Atoi(str) + + if err == nil { + return intValue, true + } + } + + return 0, false +} + +func ParseTypedQuery(values []string) interface{} { + var result interface{} + + raw := values[0] + + intValue, isInt := TryParseInt(raw) + + if strings.Contains(raw, ",") || (strings.Contains(raw, "[") && strings.Contains(raw, "]")) { + if strings.Contains(raw, "[") && strings.Contains(raw, "]") { + escapedStr := strings.ReplaceAll(raw, "[", "") + escapedStr = strings.ReplaceAll(escapedStr, "]", "") + raw = escapedStr + } + + parts := strings.Split(raw, ",") + + var list []interface{} + + for _, part := range parts { + _intValue, _isInt := TryParseInt(part) + + if _isInt { + list = append(list, _intValue) + } else { + list = append(list, part) + } + } + result = list + } else if isInt { + result = intValue + } else { + result = raw + } + + return result +} From db771b4179cb2f9a917aadaa570c8cdd0ef10c09 Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Wed, 9 Jul 2025 20:18:39 +0200 Subject: [PATCH 2/9] added compatibility middleware added Notification Payload Aliases to improve Compatibility with multiple Apps --- internals/proxy/middlewares/body.go | 88 +++++++++++++++++++++++++++++ main.go | 44 ++++++++++++++- 2 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 internals/proxy/middlewares/body.go diff --git a/internals/proxy/middlewares/body.go b/internals/proxy/middlewares/body.go new file mode 100644 index 0000000..f44f580 --- /dev/null +++ b/internals/proxy/middlewares/body.go @@ -0,0 +1,88 @@ +package middlewares + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "strconv" + + log "github.com/codeshelldev/secured-signal-api/utils/logger" +) + +type MessageAlias struct { + Alias string + Priority int +} + +type BodyMiddleware struct { + Next http.Handler + MessageAliases []MessageAlias +} + +func (data BodyMiddleware) Use() http.Handler { + next := data.Next + messageAliases := data.MessageAliases + + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if req.Body != nil { + bodyBytes, err := io.ReadAll(req.Body) + + if err != nil { + log.Error("Could not read Body: ", err.Error()) + http.Error(w, "Internal Error", http.StatusInternalServerError) + return + } + + req.Body.Close() + + var modifiedBodyData map[string]interface{} + + err = json.Unmarshal(bodyBytes, &modifiedBodyData) + + if err != nil { + log.Error("Could not decode Body: ", err.Error()) + http.Error(w, "Internal Error", http.StatusInternalServerError) + return + } + + content, ok := modifiedBodyData["message"] + + if !ok || content == "" { + best := 0 + + for _, alias := range messageAliases { + aliasKey := alias.Alias + priority := alias.Priority + + value, ok := modifiedBodyData[aliasKey] + + if ok && value != "" && priority > best { + content = modifiedBodyData[aliasKey] + } + + modifiedBodyData[aliasKey] = nil + } + + modifiedBodyData["message"] = content + + bodyBytes, err = json.Marshal(modifiedBodyData) + + if err != nil { + log.Error("Could not encode Body: ", err.Error()) + http.Error(w, "Internal Error", http.StatusInternalServerError) + return + } + } + + modifiedBody := string(bodyBytes) + + req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) + + req.ContentLength = int64(len(modifiedBody)) + req.Header.Set("Content-Length", strconv.Itoa(len(modifiedBody))) + } + + next.ServeHTTP(w, req) + }) +} diff --git a/main.go b/main.go index 1888069..03a6f73 100644 --- a/main.go +++ b/main.go @@ -26,8 +26,50 @@ func main() { initHandler = proxy.Create(ENV.API_URL) + body_m4 := BodyMiddleware{ + Next: initHandler, + MessageAliases: []MessageAlias{ + { + Alias: "msg", + Priority: 100, + }, + { + Alias: "content", + Priority: 99, + }, + { + Alias: "description", + Priority: 98, + }, + { + Alias: "text", + Priority: 20, + }, + { + Alias: "body", + Priority: 15, + }, + { + Alias: "summary", + Priority: 10, + }, + { + Alias: "details", + Priority: 9, + }, + { + Alias: "payload", + Priority: 2, + }, + { + Alias: "data", + Priority: 1, + }, + }, + } + temp_m3 := TemplateMiddleware{ - Next: initHandler, + Next: body_m4.Use(), Variables: ENV.VARIABLES, } From cb7c9c4f7bc4ee0b43f0c11eedd74034452b53e5 Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Wed, 9 Jul 2025 20:43:06 +0200 Subject: [PATCH 3/9] fixed method of checking if body is empty --- internals/proxy/middlewares/body.go | 17 ++++++++++------- internals/proxy/middlewares/template.go | 19 ++++++++++--------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/internals/proxy/middlewares/body.go b/internals/proxy/middlewares/body.go index f44f580..b91717d 100644 --- a/internals/proxy/middlewares/body.go +++ b/internals/proxy/middlewares/body.go @@ -25,14 +25,15 @@ func (data BodyMiddleware) Use() http.Handler { messageAliases := data.MessageAliases return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if req.Body != nil { - bodyBytes, err := io.ReadAll(req.Body) + bodyBytes, err := io.ReadAll(req.Body) + if err != nil { + log.Error("Could not read body:", err.Error()) + http.Error(w, "Bad Request", http.StatusBadRequest) + return + } + defer req.Body.Close() - if err != nil { - log.Error("Could not read Body: ", err.Error()) - http.Error(w, "Internal Error", http.StatusInternalServerError) - return - } + if len(bodyBytes) > 0 { req.Body.Close() @@ -81,6 +82,8 @@ func (data BodyMiddleware) Use() http.Handler { req.ContentLength = int64(len(modifiedBody)) req.Header.Set("Content-Length", strconv.Itoa(len(modifiedBody))) + } else { + req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) } next.ServeHTTP(w, req) diff --git a/internals/proxy/middlewares/template.go b/internals/proxy/middlewares/template.go index 27c2d56..9f75eb2 100644 --- a/internals/proxy/middlewares/template.go +++ b/internals/proxy/middlewares/template.go @@ -79,16 +79,15 @@ func (data TemplateMiddleware) Use() http.Handler { VARIABLES := data.Variables return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if req.Body != nil { - bodyBytes, err := io.ReadAll(req.Body) - - if err != nil { - log.Error("Could not read Body: ", err.Error()) - http.Error(w, "Internal Error", http.StatusInternalServerError) - return - } + bodyBytes, err := io.ReadAll(req.Body) + if err != nil { + log.Error("Could not read body:", err.Error()) + http.Error(w, "Bad Request", http.StatusBadRequest) + return + } + defer req.Body.Close() - req.Body.Close() + if len(bodyBytes) > 0 { var modifiedBodyData map[string]interface{} @@ -144,6 +143,8 @@ func (data TemplateMiddleware) Use() http.Handler { req.ContentLength = int64(len(modifiedBody)) req.Header.Set("Content-Length", strconv.Itoa(len(modifiedBody))) + } else { + req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) } reqPath := req.URL.Path From 2ca324fa571b35ac440998435de6c0d7c76daacc Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Wed, 9 Jul 2025 20:53:14 +0200 Subject: [PATCH 4/9] improved body updates --- internals/proxy/middlewares/auth.go | 2 -- internals/proxy/middlewares/body.go | 14 ++++++-------- internals/proxy/middlewares/template.go | 10 ++++------ 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/internals/proxy/middlewares/auth.go b/internals/proxy/middlewares/auth.go index bafebdf..aa00053 100644 --- a/internals/proxy/middlewares/auth.go +++ b/internals/proxy/middlewares/auth.go @@ -44,8 +44,6 @@ func (data AuthMiddleware) Use() http.Handler { return } - log.Info("Request:", req.Method, req.URL.Path) - authHeader := req.Header.Get("Authorization") authQuery := req.URL.Query().Get("@authorization") diff --git a/internals/proxy/middlewares/body.go b/internals/proxy/middlewares/body.go index b91717d..8ef2aa4 100644 --- a/internals/proxy/middlewares/body.go +++ b/internals/proxy/middlewares/body.go @@ -74,18 +74,16 @@ func (data BodyMiddleware) Use() http.Handler { http.Error(w, "Internal Error", http.StatusInternalServerError) return } - } - - modifiedBody := string(bodyBytes) - req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) + modifiedBody := string(bodyBytes) - req.ContentLength = int64(len(modifiedBody)) - req.Header.Set("Content-Length", strconv.Itoa(len(modifiedBody))) - } else { - req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) + req.ContentLength = int64(len(modifiedBody)) + req.Header.Set("Content-Length", strconv.Itoa(len(modifiedBody))) + } } + req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) + next.ServeHTTP(w, req) }) } diff --git a/internals/proxy/middlewares/template.go b/internals/proxy/middlewares/template.go index 9f75eb2..2d575af 100644 --- a/internals/proxy/middlewares/template.go +++ b/internals/proxy/middlewares/template.go @@ -127,7 +127,7 @@ func (data TemplateMiddleware) Use() http.Handler { log.Debug("Applied Query Templating: ", templatedQuery) } - modifiedBodyBytes, err := json.Marshal(modifiedBodyData) + bodyBytes, err = json.Marshal(modifiedBodyData) if err != nil { log.Error("Could not encode Body: ", err.Error()) @@ -135,18 +135,16 @@ func (data TemplateMiddleware) Use() http.Handler { return } - modifiedBody := string(modifiedBodyBytes) + modifiedBody := string(bodyBytes) log.Debug("Applied Body Templating: ", modifiedBody) - req.Body = io.NopCloser(bytes.NewReader(modifiedBodyBytes)) - req.ContentLength = int64(len(modifiedBody)) req.Header.Set("Content-Length", strconv.Itoa(len(modifiedBody))) - } else { - req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) } + req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) + reqPath := req.URL.Path reqPath, _ = url.PathUnescape(reqPath) From ceb4bb46b4e981ef5abd72adb2e7db16fc099431 Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Wed, 9 Jul 2025 21:19:08 +0200 Subject: [PATCH 5/9] added log middleware --- internals/proxy/middlewares/log.go | 21 +++++++++++++++++++++ main.go | 6 +++++- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 internals/proxy/middlewares/log.go diff --git a/internals/proxy/middlewares/log.go b/internals/proxy/middlewares/log.go new file mode 100644 index 0000000..ee26267 --- /dev/null +++ b/internals/proxy/middlewares/log.go @@ -0,0 +1,21 @@ +package middlewares + +import ( + "net/http" + + log "github.com/codeshelldev/secured-signal-api/utils/logger" +) + +type LogMiddleware struct { + Next http.Handler +} + +func (data LogMiddleware) Use() http.Handler { + next := data.Next + + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + log.Info("Request:", req.Method, req.URL.Path) + + next.ServeHTTP(w, req) + }) +} \ No newline at end of file diff --git a/main.go b/main.go index 03a6f73..d0e6e82 100644 --- a/main.go +++ b/main.go @@ -83,11 +83,15 @@ func main() { Token: ENV.API_TOKEN, } + log_m0 := LogMiddleware{ + Next: auth_m1.Use(), + } + log.Info("Initialized Proxy Handler") addr := "0.0.0.0:" + ENV.PORT log.Info("Server Listening on ", addr) - http.ListenAndServe(addr, auth_m1.Use()) + http.ListenAndServe(addr, log_m0.Use()) } From a6dfe1a9b2a6001abfc37ab3bed493948ee521fe Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Thu, 10 Jul 2025 11:37:10 +0200 Subject: [PATCH 6/9] added `MESSAGE_ALIASES` to ENV --- internals/proxy/middlewares/log.go | 2 +- main.go | 39 +----------------- utils/env/env.go | 63 +++++++++++++++++++++++++++--- 3 files changed, 60 insertions(+), 44 deletions(-) diff --git a/internals/proxy/middlewares/log.go b/internals/proxy/middlewares/log.go index ee26267..eac4491 100644 --- a/internals/proxy/middlewares/log.go +++ b/internals/proxy/middlewares/log.go @@ -14,7 +14,7 @@ func (data LogMiddleware) Use() http.Handler { next := data.Next return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - log.Info("Request:", req.Method, req.URL.Path) + log.Info(req.Method, req.URL.Path, "?", req.URL.RawQuery) next.ServeHTTP(w, req) }) diff --git a/main.go b/main.go index d0e6e82..ce776c0 100644 --- a/main.go +++ b/main.go @@ -28,44 +28,7 @@ func main() { body_m4 := BodyMiddleware{ Next: initHandler, - MessageAliases: []MessageAlias{ - { - Alias: "msg", - Priority: 100, - }, - { - Alias: "content", - Priority: 99, - }, - { - Alias: "description", - Priority: 98, - }, - { - Alias: "text", - Priority: 20, - }, - { - Alias: "body", - Priority: 15, - }, - { - Alias: "summary", - Priority: 10, - }, - { - Alias: "details", - Priority: 9, - }, - { - Alias: "payload", - Priority: 2, - }, - { - Alias: "data", - Priority: 1, - }, - }, + MessageAliases: ENV.MESSAGE_ALIASES, } temp_m3 := TemplateMiddleware{ diff --git a/utils/env/env.go b/utils/env/env.go index 648a0de..e6d4dc1 100644 --- a/utils/env/env.go +++ b/utils/env/env.go @@ -4,15 +4,17 @@ import ( "encoding/json" "os" + middlewares "github.com/codeshelldev/secured-signal-api/internals/proxy/middlewares" log "github.com/codeshelldev/secured-signal-api/utils/logger" ) type ENV_ struct { - PORT string - API_URL string - API_TOKEN string - BLOCKED_ENDPOINTS []string - VARIABLES map[string]any + PORT string + API_URL string + API_TOKEN string + BLOCKED_ENDPOINTS []string + VARIABLES map[string]any + MESSAGE_ALIASES []middlewares.MessageAlias } var ENV ENV_ = ENV_{ @@ -30,6 +32,44 @@ var ENV ENV_ = ENV_{ "RECIPIENTS": []string{}, "NUMBER": os.Getenv("SENDER"), }, + MESSAGE_ALIASES: []middlewares.MessageAlias{ + { + Alias: "msg", + Priority: 100, + }, + { + Alias: "content", + Priority: 99, + }, + { + Alias: "description", + Priority: 98, + }, + { + Alias: "text", + Priority: 20, + }, + { + Alias: "body", + Priority: 15, + }, + { + Alias: "summary", + Priority: 10, + }, + { + Alias: "details", + Priority: 9, + }, + { + Alias: "payload", + Priority: 2, + }, + { + Alias: "data", + Priority: 1, + }, + }, } func Load() { @@ -40,6 +80,7 @@ func Load() { blockedEndpointJSON := os.Getenv("BLOCKED_ENDPOINTS") recipientsJSON := os.Getenv("DEFAULT_RECIPIENTS") + messageAliasesJSON := os.Getenv("MESSAGE_ALIASES") variablesJSON := os.Getenv("VARIABLES") log.Info("Loaded Environment Variables") @@ -64,6 +105,18 @@ func Load() { ENV.BLOCKED_ENDPOINTS = blockedEndpoints } + if messageAliasesJSON != "" { + var msgAliases []middlewares.MessageAlias + + err := json.Unmarshal([]byte(messageAliasesJSON), &msgAliases) + + if err != nil { + log.Error("Could not decode Message Aliases ", variablesJSON) + } + + ENV.MESSAGE_ALIASES = msgAliases + } + if variablesJSON != "" { var variables map[string]interface{} From dce5445bcb4a2c07cde9e1365856ad859616f9f7 Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Thu, 10 Jul 2025 11:54:23 +0200 Subject: [PATCH 7/9] updated README --- .github/templates/README.template.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/.github/templates/README.template.md b/.github/templates/README.template.md index 10faa18..4ed7233 100644 --- a/.github/templates/README.template.md +++ b/.github/templates/README.template.md @@ -171,6 +171,31 @@ environment: DEFAULT_RECIPIENTS: ' [ "user.id", "000", "001", "group.id" ] ' ``` +#### Message Aliases + +To improve compatibility with other services Secured Signal API provides aliases for the `message` attribute by default: + +| Alias | Priority | +| ----------- | ----------- | +| msg | 100 | +| content | 99 | +| description | 98 | +| text | 20 | +| body | 15 | +| summary | 10 | +| details | 9 | +| payload | 2 | +| data | 1 | + +Secured Signal API will use the highest priority Message Alias to get the correct message from the Request Body. + +If you are missing a Message Alias to have Secured Signal API integrated with a Service you can simply set `MESSAGE_ALIASES` in ENV: + +```yaml +environment: + MESSAGE_ALIASES: ' [{ "alias": "note", "priority": 4 }, { "alias": "test", "priority": 3 }] ' +``` + ## Contributing Found a bug? Want to change or add something? From bc9e03a7d4707add9254988f40cab2aa7401f9da Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Thu, 10 Jul 2025 11:54:36 +0200 Subject: [PATCH 8/9] Minor Logging Changes --- internals/proxy/middlewares/body.go | 2 +- internals/proxy/middlewares/log.go | 2 +- internals/proxy/middlewares/template.go | 2 +- utils/logger/logger.go | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/internals/proxy/middlewares/body.go b/internals/proxy/middlewares/body.go index 8ef2aa4..73d98aa 100644 --- a/internals/proxy/middlewares/body.go +++ b/internals/proxy/middlewares/body.go @@ -27,7 +27,7 @@ func (data BodyMiddleware) Use() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { bodyBytes, err := io.ReadAll(req.Body) if err != nil { - log.Error("Could not read body:", err.Error()) + log.Error("Could not read Body: ", err.Error()) http.Error(w, "Bad Request", http.StatusBadRequest) return } diff --git a/internals/proxy/middlewares/log.go b/internals/proxy/middlewares/log.go index eac4491..6eee78e 100644 --- a/internals/proxy/middlewares/log.go +++ b/internals/proxy/middlewares/log.go @@ -14,7 +14,7 @@ func (data LogMiddleware) Use() http.Handler { next := data.Next return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - log.Info(req.Method, req.URL.Path, "?", req.URL.RawQuery) + log.Info(req.Method, " ", req.URL.Path, " ", req.URL.RawQuery) next.ServeHTTP(w, req) }) diff --git a/internals/proxy/middlewares/template.go b/internals/proxy/middlewares/template.go index 2d575af..a311452 100644 --- a/internals/proxy/middlewares/template.go +++ b/internals/proxy/middlewares/template.go @@ -81,7 +81,7 @@ func (data TemplateMiddleware) Use() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { bodyBytes, err := io.ReadAll(req.Body) if err != nil { - log.Error("Could not read body:", err.Error()) + log.Error("Could not read Body: ", err.Error()) http.Error(w, "Bad Request", http.StatusBadRequest) return } diff --git a/utils/logger/logger.go b/utils/logger/logger.go index 77ab563..fc83a10 100644 --- a/utils/logger/logger.go +++ b/utils/logger/logger.go @@ -62,19 +62,19 @@ func getLogLevel(level string) zapcore.Level { } func Info(msg ...string) { - _log.Info(strings.Join(msg, " ")) + _log.Info(strings.Join(msg, "")) } func Debug(msg ...string) { - _log.Debug(strings.Join(msg, " ")) + _log.Debug(strings.Join(msg, "")) } func Error(msg ...string) { - _log.Error(strings.Join(msg, " ")) + _log.Error(strings.Join(msg, "")) } func Warn(msg ...string) { - _log.Warn(strings.Join(msg, " ")) + _log.Warn(strings.Join(msg, "")) } From d439a1ccf72d64d2725ee45c29e5c9e2e7957658 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 09:54:46 +0000 Subject: [PATCH 9/9] Update README.md --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 698c21b..7ad6a4c 100644 --- a/README.md +++ b/README.md @@ -232,6 +232,31 @@ environment: DEFAULT_RECIPIENTS: ' [ "user.id", "000", "001", "group.id" ] ' ``` +#### Message Aliases + +To improve compatibility with other services Secured Signal API provides aliases for the `message` attribute by default: + +| Alias | Priority | +| ----------- | ----------- | +| msg | 100 | +| content | 99 | +| description | 98 | +| text | 20 | +| body | 15 | +| summary | 10 | +| details | 9 | +| payload | 2 | +| data | 1 | + +Secured Signal API will use the highest priority Message Alias to get the correct message from the Request Body. + +If you are missing a Message Alias to have Secured Signal API integrated with a Service you can simply set `MESSAGE_ALIASES` in ENV: + +```yaml +environment: + MESSAGE_ALIASES: ' [{ "alias": "note", "priority": 4 }, { "alias": "test", "priority": 3 }] ' +``` + ## Contributing Found a bug? Want to change or add something?