From 95ec6e02a13d535d61705c7bc8f74af50123aa5d Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Sat, 6 Sep 2025 17:13:32 +0200 Subject: [PATCH 01/18] updated go to 1.25.1 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 62c830e..a647fb6 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/codeshelldev/secured-signal-api -go 1.24.5 +go 1.25.1 require go.uber.org/zap v1.27.0 From 32e2fe845c347a5092528e045a3c5cfa4505a5ca Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Sat, 6 Sep 2025 17:13:52 +0200 Subject: [PATCH 02/18] changed dot-Import to named --- main.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/main.go b/main.go index c430634..3ea3b6e 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,7 @@ import ( "os" proxy "github.com/codeshelldev/secured-signal-api/internals/proxy" - . "github.com/codeshelldev/secured-signal-api/internals/proxy/middlewares" + middlewares "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" ) @@ -26,27 +26,27 @@ func main() { initHandler = proxy.Create(ENV.API_URL) - body_m4 := BodyMiddleware{ + body_m4 := middlewares.BodyMiddleware{ Next: initHandler, MessageAliases: ENV.MESSAGE_ALIASES, } - temp_m3 := TemplateMiddleware{ + temp_m3 := middlewares.TemplateMiddleware{ Next: body_m4.Use(), Variables: ENV.VARIABLES, } - endp_m2 := EndpointsMiddleware{ + endp_m2 := middlewares.EndpointsMiddleware{ Next: temp_m3.Use(), BlockedEndpoints: ENV.BLOCKED_ENDPOINTS, } - auth_m1 := AuthMiddleware{ + auth_m1 := middlewares.AuthMiddleware{ Next: endp_m2.Use(), Tokens: ENV.API_TOKENS, } - log_m0 := LogMiddleware{ + log_m0 := middlewares.LogMiddleware{ Next: auth_m1.Use(), } From 6f72f1a89cd2931b7b61f832996ec9408fa83aa2 Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Sat, 6 Sep 2025 17:14:09 +0200 Subject: [PATCH 03/18] cleaned up templating logic --- internals/proxy/middlewares/template.go | 111 ++++++++++-------- utils/query/query.go | 6 +- utils/request/request.go | 150 ++++++++++++++++++++++++ 3 files changed, 218 insertions(+), 49 deletions(-) create mode 100644 utils/request/request.go diff --git a/internals/proxy/middlewares/template.go b/internals/proxy/middlewares/template.go index a311452..890f70a 100644 --- a/internals/proxy/middlewares/template.go +++ b/internals/proxy/middlewares/template.go @@ -13,6 +13,7 @@ import ( log "github.com/codeshelldev/secured-signal-api/utils/logger" query "github.com/codeshelldev/secured-signal-api/utils/query" + request "github.com/codeshelldev/secured-signal-api/utils/request" ) type TemplateMiddleware struct { @@ -79,79 +80,97 @@ func (data TemplateMiddleware) Use() http.Handler { VARIABLES := data.Variables 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()) - http.Error(w, "Bad Request", http.StatusBadRequest) - return + var body request.Body + body = request.GetReqBody(w, req) + + bodyData := map[string]interface{}{} + + var modifiedBody bool + + if !body.Empty { + bodyData = templateJSON(body.Data, VARIABLES) + + modifiedBody = true } - defer req.Body.Close() - if len(bodyBytes) > 0 { + if req.URL.RawQuery != "" { + req.URL.RawQuery, bodyData = templateQuery(req.URL, VARIABLES) - var modifiedBodyData map[string]interface{} + modifiedBody = true + } - err = json.Unmarshal(bodyBytes, &modifiedBodyData) + if modifiedBody { + modifiedBody, err := request.CreateBody(bodyData) if err != nil { - log.Error("Could not decode Body: ", err.Error()) http.Error(w, "Internal Error", http.StatusInternalServerError) return } - modifiedBodyData = templateJSON(modifiedBodyData, VARIABLES) + body = modifiedBody - if req.URL.RawQuery != "" { - decodedQuery, _ := url.QueryUnescape(req.URL.RawQuery) + strData := body.ToString() - log.Debug("Decoded Query: ", decodedQuery) + log.Debug("Applied Body Templating: ", strData) - templatedQuery, _ := renderTemplate("query", decodedQuery, VARIABLES) + req.ContentLength = int64(len(strData)) + req.Header.Set("Content-Length", strconv.Itoa(len(strData))) + } - modifiedQuery := req.URL.Query() + req.Body = io.NopCloser(bytes.NewReader(body.Raw)) - queryData := query.ParseRawQuery(templatedQuery) + req.URL.Path = templatePath(req.URL, VARIABLES) - for key, value := range queryData { - keyWithoutPrefix, found := strings.CutPrefix(key, "@") + next.ServeHTTP(w, req) + }) +} - if found { - modifiedBodyData[keyWithoutPrefix] = query.ParseTypedQuery(value) +func templatePath(reqUrl *url.URL, VARIABLES interface{}) string { + reqPath, err := url.PathUnescape(reqUrl.Path) - modifiedQuery.Del(key) - } - } + if err != nil { + log.Error("Error while Escaping Path: ", err.Error()) + return reqUrl.Path + } - req.URL.RawQuery = modifiedQuery.Encode() + reqPath, err = renderTemplate("path", reqPath, VARIABLES) - log.Debug("Applied Query Templating: ", templatedQuery) - } + if err != nil { + log.Error("Could not Template Path: ", err.Error()) + return reqUrl.Path + } - bodyBytes, err = json.Marshal(modifiedBodyData) + log.Debug("Applied Path Templating: ", reqPath) - if err != nil { - log.Error("Could not encode Body: ", err.Error()) - http.Error(w, "Internal Error", http.StatusInternalServerError) - return - } + return reqPath +} - modifiedBody := string(bodyBytes) +func templateQuery(reqUrl *url.URL, VARIABLES interface{}) (string, map[string]interface{}) { + data := map[string]interface{}{} - log.Debug("Applied Body Templating: ", modifiedBody) + decodedQuery, _ := url.QueryUnescape(reqUrl.RawQuery) - req.ContentLength = int64(len(modifiedBody)) - req.Header.Set("Content-Length", strconv.Itoa(len(modifiedBody))) - } + log.Debug("Decoded Query: ", decodedQuery) - req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) + templatedQuery, _ := renderTemplate("query", decodedQuery, VARIABLES) - reqPath := req.URL.Path - reqPath, _ = url.PathUnescape(reqPath) + modifiedQuery := reqUrl.Query() - modifiedReqPath, _ := renderTemplate("path", reqPath, VARIABLES) + queryData := query.ParseRawQuery(templatedQuery) - req.URL.Path = modifiedReqPath + for key, value := range queryData { + keyWithoutPrefix, found := strings.CutPrefix(key, "@") - next.ServeHTTP(w, req) - }) -} + if found { + data[keyWithoutPrefix] = query.ParseTypedQuery(value) + + modifiedQuery.Del(key) + } + } + + reqUrl.RawQuery = modifiedQuery.Encode() + + log.Debug("Applied Query Templating: ", templatedQuery) + + return reqUrl.RawQuery, data +} \ No newline at end of file diff --git a/utils/query/query.go b/utils/query/query.go index 789f33f..04b00da 100644 --- a/utils/query/query.go +++ b/utils/query/query.go @@ -30,7 +30,7 @@ func ParseRawQuery(raw string) map[string][]string { return result } -func TryParseInt(str string) (int, bool) { +func tryParseInt(str string) (int, bool) { isInt, err := regexp.MatchString(`^\d+$`, str) if isInt && err == nil { @@ -49,7 +49,7 @@ func ParseTypedQuery(values []string) interface{} { raw := values[0] - intValue, isInt := TryParseInt(raw) + intValue, isInt := tryParseInt(raw) if strings.Contains(raw, ",") || (strings.Contains(raw, "[") && strings.Contains(raw, "]")) { if strings.Contains(raw, "[") && strings.Contains(raw, "]") { @@ -63,7 +63,7 @@ func ParseTypedQuery(values []string) interface{} { var list []interface{} for _, part := range parts { - _intValue, _isInt := TryParseInt(part) + _intValue, _isInt := tryParseInt(part) if _isInt { list = append(list, _intValue) diff --git a/utils/request/request.go b/utils/request/request.go new file mode 100644 index 0000000..6239970 --- /dev/null +++ b/utils/request/request.go @@ -0,0 +1,150 @@ +package req + +import ( + "encoding/json" + "errors" + "io" + "net/http" + "strings" + + log "github.com/codeshelldev/secured-signal-api/utils/logger" + "github.com/codeshelldev/secured-signal-api/utils/query" +) + +const ( + Json BodyType = "Json" + Form BodyType = "Form" + Unknown BodyType = "Unknown" +) + +type BodyType string + +type Body struct { + Data map[string]interface{} + Raw []byte + Empty bool +} + +func (body Body) ToString() string { + return string(body.Raw) +} + +func CreateBody(data map[string]interface{}) (Body, error) { + if len(data) <= 0 { + err := errors.New("empty data map") + log.Error("Could not encode Body: ", err.Error()) + return Body{Empty: true}, err + } + + bytes, err := json.Marshal(data) + + if err != nil { + log.Error("Could not encode Body: ", err.Error()) + return Body{Empty: true}, err + } + + isEmpty := len(data) <= 0 + + return Body{ + Data: data, + Raw: bytes, + Empty: isEmpty, + }, nil +} + +func GetJsonData(body []byte) (map[string]interface{}, error) { + var data map[string]interface{} + + err := json.Unmarshal(body, &data) + + if err != nil { + log.Error("Could not decode Body: ", err.Error()) + return nil, err + } + + return data, nil +} + +func GetFormData(body []byte) (map[string]interface{}, error) { + data := map[string]interface{}{} + + queryData := query.ParseRawQuery(string(body)) + + if len(queryData) <= 0 { + err := errors.New("invalid form data") + log.Error("Could not decode Body: ", err.Error()) + return nil, err + } + + for key, value := range queryData { + data[key] = query.ParseTypedQuery(value) + } + + return data, nil +} + +func GetBody(req *http.Request) ([]byte, error) { + bodyBytes, err := io.ReadAll(req.Body) + + if err != nil { + log.Error("Could not read Body: ", err.Error()) + return nil, err + } + defer req.Body.Close() + + return bodyBytes, nil +} + +func GetReqBody(w http.ResponseWriter, req *http.Request) Body { + bytes, err := GetBody(req) + + if err != nil { + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + req.Body.Close() + + return Body{Empty: true} + } + defer req.Body.Close() + + var data map[string]interface{} + + switch GetBodyType(req) { + case Json: + data, err = GetJsonData(bytes) + + if err != nil { + http.Error(w, "Bad Request: invalid JSON", http.StatusBadRequest) + } + case Form: + data, err = GetFormData(bytes) + + if err != nil { + http.Error(w, "Bad Request: invalid Form", http.StatusBadRequest) + } + } + + isEmpty := len(data) <= 0 + + return Body{ + Raw: bytes, + Data: data, + Empty: isEmpty, + } +} + +func GetBodyType(req *http.Request) BodyType { + contentType := req.Header.Get("Content-Type") + + switch { + case strings.HasPrefix(contentType, "application/json"): + return Json + + case strings.HasPrefix(contentType, "multipart/form-data"): + return Form + + case strings.HasPrefix(contentType, "application/x-www-form-urlencoded"): + return Form + default: + return Unknown + } +} \ No newline at end of file From cd2dd7e13580d5a61e374d2467e0b50b0c6df2a4 Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Sat, 6 Sep 2025 17:29:22 +0200 Subject: [PATCH 04/18] cleaned message aliassing --- internals/proxy/middlewares/body.go | 82 ++++++++++++++--------------- 1 file changed, 39 insertions(+), 43 deletions(-) diff --git a/internals/proxy/middlewares/body.go b/internals/proxy/middlewares/body.go index 73d98aa..1be9329 100644 --- a/internals/proxy/middlewares/body.go +++ b/internals/proxy/middlewares/body.go @@ -2,12 +2,11 @@ package middlewares import ( "bytes" - "encoding/json" "io" "net/http" "strconv" - log "github.com/codeshelldev/secured-signal-api/utils/logger" + request "github.com/codeshelldev/secured-signal-api/utils/request" ) type MessageAlias struct { @@ -25,65 +24,62 @@ func (data BodyMiddleware) Use() http.Handler { messageAliases := data.MessageAliases 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()) - http.Error(w, "Bad Request", http.StatusBadRequest) - return - } - defer req.Body.Close() + body := request.GetReqBody(w, req) + + var modifiedBody bool + var bodyData map[string]interface{} - if len(bodyBytes) > 0 { + if !body.Empty { + bodyData = body.Data - req.Body.Close() + content, ok := bodyData["message"] - var modifiedBodyData map[string]interface{} + if !ok || content == "" { - err = json.Unmarshal(bodyBytes, &modifiedBodyData) + bodyData["message"], bodyData = getMessage(messageAliases, bodyData) + + modifiedBody = true + } + } + + if modifiedBody { + modifiedBody, err := request.CreateBody(bodyData) 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 + body = modifiedBody - value, ok := modifiedBodyData[aliasKey] + strData := body.ToString() - if ok && value != "" && priority > best { - content = modifiedBodyData[aliasKey] - } + req.ContentLength = int64(len(strData)) + req.Header.Set("Content-Length", strconv.Itoa(len(strData))) + } - modifiedBodyData[aliasKey] = nil - } + req.Body = io.NopCloser(bytes.NewReader(body.Raw)) - modifiedBodyData["message"] = content + next.ServeHTTP(w, req) + }) +} - bodyBytes, err = json.Marshal(modifiedBodyData) +func getMessage(aliases []MessageAlias, data map[string]interface{}) (string, map[string]interface{}) { + var content string + var best int - if err != nil { - log.Error("Could not encode Body: ", err.Error()) - http.Error(w, "Internal Error", http.StatusInternalServerError) - return - } + for _, alias := range aliases { + aliasKey := alias.Alias + priority := alias.Priority - modifiedBody := string(bodyBytes) + value, ok := data[aliasKey] - req.ContentLength = int64(len(modifiedBody)) - req.Header.Set("Content-Length", strconv.Itoa(len(modifiedBody))) - } + if ok && value != "" && priority > best { + content = data[aliasKey].(string) } - req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) + data[aliasKey] = nil + } - next.ServeHTTP(w, req) - }) -} + return content, data +} \ No newline at end of file From fa3e351de062652b9eed8ced3a010c323ac58b6f Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Sat, 6 Sep 2025 17:31:53 +0200 Subject: [PATCH 05/18] rearranged functions --- internals/proxy/middlewares/template.go | 100 ++++++++++++------------ 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/internals/proxy/middlewares/template.go b/internals/proxy/middlewares/template.go index 890f70a..0e41b4f 100644 --- a/internals/proxy/middlewares/template.go +++ b/internals/proxy/middlewares/template.go @@ -21,6 +21,56 @@ type TemplateMiddleware struct { Variables map[string]interface{} } +func (data TemplateMiddleware) Use() http.Handler { + next := data.Next + VARIABLES := data.Variables + + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + var body request.Body + body = request.GetReqBody(w, req) + + bodyData := map[string]interface{}{} + + var modifiedBody bool + + if !body.Empty { + bodyData = templateJSON(body.Data, VARIABLES) + + modifiedBody = true + } + + if req.URL.RawQuery != "" { + req.URL.RawQuery, bodyData = templateQuery(req.URL, VARIABLES) + + modifiedBody = true + } + + if modifiedBody { + modifiedBody, err := request.CreateBody(bodyData) + + if err != nil { + http.Error(w, "Internal Error", http.StatusInternalServerError) + return + } + + body = modifiedBody + + strData := body.ToString() + + log.Debug("Applied Body Templating: ", strData) + + req.ContentLength = int64(len(strData)) + req.Header.Set("Content-Length", strconv.Itoa(len(strData))) + } + + req.Body = io.NopCloser(bytes.NewReader(body.Raw)) + + req.URL.Path = templatePath(req.URL, VARIABLES) + + next.ServeHTTP(w, req) + }) +} + func renderTemplate(name string, tmplStr string, data any) (string, error) { tmpl, err := template.New(name).Parse(tmplStr) @@ -75,56 +125,6 @@ func templateJSON(data map[string]interface{}, variables map[string]interface{}) return data } -func (data TemplateMiddleware) Use() http.Handler { - next := data.Next - VARIABLES := data.Variables - - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - var body request.Body - body = request.GetReqBody(w, req) - - bodyData := map[string]interface{}{} - - var modifiedBody bool - - if !body.Empty { - bodyData = templateJSON(body.Data, VARIABLES) - - modifiedBody = true - } - - if req.URL.RawQuery != "" { - req.URL.RawQuery, bodyData = templateQuery(req.URL, VARIABLES) - - modifiedBody = true - } - - if modifiedBody { - modifiedBody, err := request.CreateBody(bodyData) - - if err != nil { - http.Error(w, "Internal Error", http.StatusInternalServerError) - return - } - - body = modifiedBody - - strData := body.ToString() - - log.Debug("Applied Body Templating: ", strData) - - req.ContentLength = int64(len(strData)) - req.Header.Set("Content-Length", strconv.Itoa(len(strData))) - } - - req.Body = io.NopCloser(bytes.NewReader(body.Raw)) - - req.URL.Path = templatePath(req.URL, VARIABLES) - - next.ServeHTTP(w, req) - }) -} - func templatePath(reqUrl *url.URL, VARIABLES interface{}) string { reqPath, err := url.PathUnescape(reqUrl.Path) From e87bbdf73d2856fe243944113c0758a7478058cd Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Sat, 6 Sep 2025 18:11:56 +0200 Subject: [PATCH 06/18] added fatal logging --- utils/logger/logger.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/utils/logger/logger.go b/utils/logger/logger.go index 7847ec2..78887b3 100644 --- a/utils/logger/logger.go +++ b/utils/logger/logger.go @@ -56,6 +56,8 @@ func getLogLevel(level string) zapcore.Level { return zapcore.WarnLevel case "error": return zapcore.ErrorLevel + case "fatal": + return zapcore.FatalLevel default: return zapcore.InfoLevel } @@ -73,11 +75,14 @@ func Error(msg ...string) { _log.Error(strings.Join(msg, "")) } +func Fatal(msg ...string) { + _log.Fatal(strings.Join(msg, "")) +} + func Warn(msg ...string) { _log.Warn(strings.Join(msg, "")) } - func Sync() { _ = _log.Sync() -} +} \ No newline at end of file From 9cea14f7d997b44ef19c714be6a397bb1fd0a6e5 Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Sat, 6 Sep 2025 18:12:07 +0200 Subject: [PATCH 07/18] cleaned up utils --- utils/utils.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/utils/utils.go b/utils/utils.go index 67368ff..c601461 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -7,27 +7,26 @@ package utils import ( "encoding/json" - "errors" "regexp" "strings" ) -func StringToArray(sliceStr string) ([]string, error) { +func StringToArray(sliceStr string) []string { if sliceStr == "" { - return []string{}, errors.New("sliceStr is empty") + return nil } re, err := regexp.Compile(`\s+`) if err != nil { - return []string{}, err + return nil } normalized := re.ReplaceAllString(sliceStr, "") tokens := strings.Split(normalized, ",") - return tokens, nil + return tokens } func GetJsonSafe[T any](jsonStr string) (T, error) { From b31505e9728d453261ba2cdb0367741ceda72618 Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Sat, 6 Sep 2025 18:12:39 +0200 Subject: [PATCH 08/18] added deprecation to json for `RECIPIENTS` and `BLOCKED_ENDOINTS` --- utils/env/env.go | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/utils/env/env.go b/utils/env/env.go index f116d71..4bfce98 100644 --- a/utils/env/env.go +++ b/utils/env/env.go @@ -3,9 +3,11 @@ package env import ( "os" "strconv" + "strings" middlewares "github.com/codeshelldev/secured-signal-api/internals/proxy/middlewares" "github.com/codeshelldev/secured-signal-api/utils" + "github.com/codeshelldev/secured-signal-api/utils/docker" log "github.com/codeshelldev/secured-signal-api/utils/logger" ) @@ -83,16 +85,17 @@ func Load() { apiToken = os.Getenv("API_TOKEN") } - blockedEndpointJSON := os.Getenv("BLOCKED_ENDPOINTS") - recipientsJSON := os.Getenv("RECIPIENTS") + blockedEndpointStrArray := os.Getenv("BLOCKED_ENDPOINTS") + recipientsStrArray := os.Getenv("RECIPIENTS") + messageAliasesJSON := os.Getenv("MESSAGE_ALIASES") variablesJSON := os.Getenv("VARIABLES") log.Info("Loaded Environment Variables") - apiTokens, err := utils.StringToArray(apiToken) + apiTokens := utils.StringToArray(apiToken) - if err != nil { + if apiTokens == nil { log.Warn("No API TOKEN provided this is NOT recommended") log.Info("Disabling Security Features due to incomplete Congfiguration") @@ -104,8 +107,17 @@ func Load() { ENV.API_TOKENS = apiTokens } - if blockedEndpointJSON != "" { - ENV.BLOCKED_ENDPOINTS = utils.GetJson[[]string](blockedEndpointJSON) + if blockedEndpointStrArray != "" { + if strings.Contains(blockedEndpointStrArray, "[") || strings.Contains(blockedEndpointStrArray, "]") { + //! Deprecated: JSON + //TODO: Remove this in new Version + + log.Error("Invalid Blocked Endpoints: ", "JSON instead of Comma seperated String") + + docker.Exit(1) + } + + ENV.BLOCKED_ENDPOINTS = utils.StringToArray(blockedEndpointStrArray) } if messageAliasesJSON != "" { @@ -116,7 +128,16 @@ func Load() { ENV.VARIABLES = utils.GetJson[map[string]any](variablesJSON) } - if recipientsJSON != "" { - ENV.VARIABLES["RECIPIENTS"] = utils.GetJson[[]string](recipientsJSON) + if recipientsStrArray != "" { + if strings.Contains(blockedEndpointStrArray, "[") || strings.Contains(blockedEndpointStrArray, "]") { + //! Deprecated: JSON + //TODO: Remove this in new Version + + log.Error("Invalid Blocked Endpoints: ", "JSON instead of Comma seperated String") + + docker.Exit(1) + } + + ENV.VARIABLES["RECIPIENTS"] = utils.StringToArray(recipientsStrArray) } } \ No newline at end of file From f77cd6ed7bd94177ad09ed436703500f7a99d670 Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Sat, 6 Sep 2025 18:12:48 +0200 Subject: [PATCH 09/18] added docker shutdown logic --- main.go | 20 +++++++++++++++-- utils/docker/docker.go | 50 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 utils/docker/docker.go diff --git a/main.go b/main.go index 3ea3b6e..b4b434c 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( proxy "github.com/codeshelldev/secured-signal-api/internals/proxy" middlewares "github.com/codeshelldev/secured-signal-api/internals/proxy/middlewares" + docker "github.com/codeshelldev/secured-signal-api/utils/docker" env "github.com/codeshelldev/secured-signal-api/utils/env" log "github.com/codeshelldev/secured-signal-api/utils/logger" ) @@ -56,5 +57,20 @@ func main() { log.Info("Server Listening on ", addr) - http.ListenAndServe(addr, log_m0.Use()) -} + server := &http.Server{ + Addr: addr, + Handler: log_m0.Use(), + } + + stop := docker.Run(func(){ + err := server.ListenAndServe() + + if err != nil && err != http.ErrServerClosed { + log.Fatal("Server error: ", err.Error()) + } + }) + + <-stop + + docker.Shutdown(server) +} \ No newline at end of file diff --git a/utils/docker/docker.go b/utils/docker/docker.go new file mode 100644 index 0000000..8635c12 --- /dev/null +++ b/utils/docker/docker.go @@ -0,0 +1,50 @@ +package docker + +import ( + "context" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + log "github.com/codeshelldev/secured-signal-api/utils/logger" +) + +var stop chan os.Signal + +func Run(main func()) chan os.Signal { + stop = make(chan os.Signal, 1) + signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) + + go main() + + return stop +} + +func Exit(code int) { + log.Info("Exiting...") + + os.Exit(code) + + stop <- syscall.SIGTERM +} + +func Shutdown(server *http.Server) { + log.Info("Shutdown signal received") + + log.Sync() + + ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second) + defer cancel() + + err := server.Shutdown(ctx); + + if err != nil { + log.Fatal("Server shutdown failed: ", err.Error()) + + log.Info("Server exited forcefully") + } else { + log.Info("Server exited gracefully") + } +} \ No newline at end of file From f5e928a121cdf434e8de2c6748bd6c5a0a80fba4 Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Sat, 6 Sep 2025 18:16:41 +0200 Subject: [PATCH 10/18] updated README to reflect changes --- .github/templates/README.template.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/templates/README.template.md b/.github/templates/README.template.md index c53c812..3bdd98d 100644 --- a/.github/templates/README.template.md +++ b/.github/templates/README.template.md @@ -177,11 +177,15 @@ Because Secured Signal API is just a Proxy you can use all of the [Signal REST A | **/v1/accounts** | | **/v1/contacts** | -These Endpoints are blocked by default due to Security Risks, but can be modified by setting `BLOCKED_ENDPOINTS` to a valid json array string +These Endpoints are blocked by default due to Security Risks, but can be modified by setting `BLOCKED_ENDPOINTS` to a Comma seperated List: ```yaml environment: - BLOCKED_ENDPOINTS: '[ "/v1/register","/v1/unregister","/v1/qrcodelink","/v1/contacts" ]' + BLOCKED_ENDPOINTS: | + /v1/register, + /v1/unregister, + /v1/qrcodelink, + /v1/contacts, ``` #### Variables @@ -206,7 +210,8 @@ Set this Environment Variable to automatically provide default Recipients: ```yaml environment: - RECIPIENTS: ' [ "user.id", "000", "001", "group.id" ] ' + RECIPIENTS: | + user.id, 000, 001, group.id, ``` example: From ac76cef73e247ad3a3893404dff3aaf45e39395b Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Sat, 6 Sep 2025 18:25:28 +0200 Subject: [PATCH 11/18] fix empty-body handling --- utils/request/request.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/utils/request/request.go b/utils/request/request.go index 6239970..bdbd7e3 100644 --- a/utils/request/request.go +++ b/utils/request/request.go @@ -88,6 +88,9 @@ func GetBody(req *http.Request) ([]byte, error) { if err != nil { log.Error("Could not read Body: ", err.Error()) + + req.Body.Close() + return nil, err } defer req.Body.Close() @@ -97,14 +100,20 @@ func GetBody(req *http.Request) ([]byte, error) { func GetReqBody(w http.ResponseWriter, req *http.Request) Body { bytes, err := GetBody(req) + + var isEmpty bool + + isEmpty = len(bytes) > 0 + + if isEmpty { + return Body{Empty: true} + } if err != nil { http.Error(w, "Internal Server Error", http.StatusInternalServerError) - req.Body.Close() return Body{Empty: true} } - defer req.Body.Close() var data map[string]interface{} @@ -123,7 +132,7 @@ func GetReqBody(w http.ResponseWriter, req *http.Request) Body { } } - isEmpty := len(data) <= 0 + isEmpty = len(data) <= 0 return Body{ Raw: bytes, From c5c29470f9a4f50efa648d056e0ba83c85a94d97 Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Sat, 6 Sep 2025 18:32:13 +0200 Subject: [PATCH 12/18] debugging --- utils/request/request.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/utils/request/request.go b/utils/request/request.go index bdbd7e3..a34dcf5 100644 --- a/utils/request/request.go +++ b/utils/request/request.go @@ -102,16 +102,18 @@ func GetReqBody(w http.ResponseWriter, req *http.Request) Body { bytes, err := GetBody(req) var isEmpty bool - - isEmpty = len(bytes) > 0 - - if isEmpty { - return Body{Empty: true} - } if err != nil { http.Error(w, "Internal Server Error", http.StatusInternalServerError) + isEmpty = true + } + + if len(bytes) <= 0 { + isEmpty = true + } + + if isEmpty { return Body{Empty: true} } From 56a8f87d988507b074cd573115f50cdba12974ed Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Sat, 6 Sep 2025 19:00:30 +0200 Subject: [PATCH 13/18] improved error handling and fixed modified flag --- internals/proxy/middlewares/body.go | 7 ++- internals/proxy/middlewares/template.go | 67 ++++++++++++++++++------- utils/request/request.go | 27 +++++----- 3 files changed, 66 insertions(+), 35 deletions(-) diff --git a/internals/proxy/middlewares/body.go b/internals/proxy/middlewares/body.go index 1be9329..a4f245f 100644 --- a/internals/proxy/middlewares/body.go +++ b/internals/proxy/middlewares/body.go @@ -6,6 +6,7 @@ import ( "net/http" "strconv" + log "github.com/codeshelldev/secured-signal-api/utils/logger" request "github.com/codeshelldev/secured-signal-api/utils/request" ) @@ -24,7 +25,11 @@ func (data BodyMiddleware) Use() http.Handler { messageAliases := data.MessageAliases return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - body := request.GetReqBody(w, req) + body, err := request.GetReqBody(w, req) + + if err != nil { + log.Error("Could not get Request Body: ", err.Error()) + } var modifiedBody bool var bodyData map[string]interface{} diff --git a/internals/proxy/middlewares/template.go b/internals/proxy/middlewares/template.go index 0e41b4f..c96e881 100644 --- a/internals/proxy/middlewares/template.go +++ b/internals/proxy/middlewares/template.go @@ -26,23 +26,34 @@ func (data TemplateMiddleware) Use() http.Handler { VARIABLES := data.Variables return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - var body request.Body - body = request.GetReqBody(w, req) + body, err := request.GetReqBody(w, req) + + if err != nil { + log.Error("Could not get Request Body: ", err.Error()) + } bodyData := map[string]interface{}{} var modifiedBody bool if !body.Empty { - bodyData = templateJSON(body.Data, VARIABLES) + var modified bool - modifiedBody = true + bodyData, modified = templateJSON(body.Data, VARIABLES) + + if modified { + modifiedBody = true + } } if req.URL.RawQuery != "" { - req.URL.RawQuery, bodyData = templateQuery(req.URL, VARIABLES) + var modified bool - modifiedBody = true + req.URL.RawQuery, bodyData, modified = templateQuery(req.URL, VARIABLES) + + if modified { + modifiedBody = true + } } if modifiedBody { @@ -65,7 +76,7 @@ func (data TemplateMiddleware) Use() http.Handler { req.Body = io.NopCloser(bytes.NewReader(body.Raw)) - req.URL.Path = templatePath(req.URL, VARIABLES) + req.URL.Path, _ = templatePath(req.URL, VARIABLES) next.ServeHTTP(w, req) }) @@ -87,7 +98,9 @@ func renderTemplate(name string, tmplStr string, data any) (string, error) { return buf.String(), nil } -func templateJSON(data map[string]interface{}, variables map[string]interface{}) map[string]interface{} { +func templateJSON(data map[string]interface{}, variables map[string]interface{}) (map[string]interface{}, bool) { + var modified bool + for k, v := range data { str, ok := v.(string) @@ -95,7 +108,7 @@ func templateJSON(data map[string]interface{}, variables map[string]interface{}) 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()) + log.Error("Error while Compiling Regex: ", err.Error()) } matches := re.FindAllStringSubmatch(str, -1) @@ -114,38 +127,50 @@ func templateJSON(data map[string]interface{}, variables map[string]interface{}) data[k] = strings.ReplaceAll(str, string(variable), tmplStr[0]) } + + modified = true } else if len(matches) == 1 { tmplKey := matches[0][1] data[k] = variables[tmplKey] + + modified = true } } } - return data + return data, modified } -func templatePath(reqUrl *url.URL, VARIABLES interface{}) string { +func templatePath(reqUrl *url.URL, VARIABLES interface{}) (string, bool) { + var modified bool + reqPath, err := url.PathUnescape(reqUrl.Path) if err != nil { log.Error("Error while Escaping Path: ", err.Error()) - return reqUrl.Path + return reqUrl.Path, modified } reqPath, err = renderTemplate("path", reqPath, VARIABLES) if err != nil { log.Error("Could not Template Path: ", err.Error()) - return reqUrl.Path + return reqUrl.Path, modified } - log.Debug("Applied Path Templating: ", reqPath) + if reqUrl.Path != reqPath { + log.Debug("Applied Path Templating: ", reqPath) + + modified = true + } - return reqPath + return reqPath, modified } -func templateQuery(reqUrl *url.URL, VARIABLES interface{}) (string, map[string]interface{}) { +func templateQuery(reqUrl *url.URL, VARIABLES interface{}) (string, map[string]interface{}, bool) { + var modified bool + data := map[string]interface{}{} decodedQuery, _ := url.QueryUnescape(reqUrl.RawQuery) @@ -168,9 +193,13 @@ func templateQuery(reqUrl *url.URL, VARIABLES interface{}) (string, map[string]i } } - reqUrl.RawQuery = modifiedQuery.Encode() + reqRawQuery := modifiedQuery.Encode() + + if reqUrl.RawQuery != reqRawQuery { + log.Debug("Applied Query Templating: ", templatedQuery) - log.Debug("Applied Query Templating: ", templatedQuery) + modified = true + } - return reqUrl.RawQuery, data + return reqRawQuery, data, modified } \ No newline at end of file diff --git a/utils/request/request.go b/utils/request/request.go index a34dcf5..72095e7 100644 --- a/utils/request/request.go +++ b/utils/request/request.go @@ -7,7 +7,6 @@ import ( "net/http" "strings" - log "github.com/codeshelldev/secured-signal-api/utils/logger" "github.com/codeshelldev/secured-signal-api/utils/query" ) @@ -32,14 +31,14 @@ func (body Body) ToString() string { func CreateBody(data map[string]interface{}) (Body, error) { if len(data) <= 0 { err := errors.New("empty data map") - log.Error("Could not encode Body: ", err.Error()) + return Body{Empty: true}, err } bytes, err := json.Marshal(data) if err != nil { - log.Error("Could not encode Body: ", err.Error()) + return Body{Empty: true}, err } @@ -58,7 +57,7 @@ func GetJsonData(body []byte) (map[string]interface{}, error) { err := json.Unmarshal(body, &data) if err != nil { - log.Error("Could not decode Body: ", err.Error()) + return nil, err } @@ -72,7 +71,7 @@ func GetFormData(body []byte) (map[string]interface{}, error) { if len(queryData) <= 0 { err := errors.New("invalid form data") - log.Error("Could not decode Body: ", err.Error()) + return nil, err } @@ -87,8 +86,6 @@ func GetBody(req *http.Request) ([]byte, error) { bodyBytes, err := io.ReadAll(req.Body) if err != nil { - log.Error("Could not read Body: ", err.Error()) - req.Body.Close() return nil, err @@ -98,7 +95,7 @@ func GetBody(req *http.Request) ([]byte, error) { return bodyBytes, nil } -func GetReqBody(w http.ResponseWriter, req *http.Request) Body { +func GetReqBody(w http.ResponseWriter, req *http.Request) (Body, error) { bytes, err := GetBody(req) var isEmpty bool @@ -106,15 +103,11 @@ func GetReqBody(w http.ResponseWriter, req *http.Request) Body { if err != nil { http.Error(w, "Internal Server Error", http.StatusInternalServerError) - isEmpty = true + return Body{Empty: true}, err } if len(bytes) <= 0 { - isEmpty = true - } - - if isEmpty { - return Body{Empty: true} + return Body{Empty: true}, errors.New("request body is empty") } var data map[string]interface{} @@ -125,12 +118,16 @@ func GetReqBody(w http.ResponseWriter, req *http.Request) Body { if err != nil { http.Error(w, "Bad Request: invalid JSON", http.StatusBadRequest) + + return Body{Empty: true}, err } case Form: data, err = GetFormData(bytes) if err != nil { http.Error(w, "Bad Request: invalid Form", http.StatusBadRequest) + + return Body{Empty: true}, err } } @@ -140,7 +137,7 @@ func GetReqBody(w http.ResponseWriter, req *http.Request) Body { Raw: bytes, Data: data, Empty: isEmpty, - } + }, nil } func GetBodyType(req *http.Request) BodyType { From 90588bcb761180fefc3c3f315f407828a4d3609a Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Sat, 6 Sep 2025 19:06:40 +0200 Subject: [PATCH 14/18] better+ error handling :) --- utils/request/request.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/request/request.go b/utils/request/request.go index 72095e7..9e5c12c 100644 --- a/utils/request/request.go +++ b/utils/request/request.go @@ -107,7 +107,7 @@ func GetReqBody(w http.ResponseWriter, req *http.Request) (Body, error) { } if len(bytes) <= 0 { - return Body{Empty: true}, errors.New("request body is empty") + return Body{Empty: true}, nil } var data map[string]interface{} From 551bd4b90c44174ed1c13eef0c24a774c4e9b7fc Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Sat, 6 Sep 2025 19:14:30 +0200 Subject: [PATCH 15/18] debug fix --- internals/proxy/middlewares/template.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internals/proxy/middlewares/template.go b/internals/proxy/middlewares/template.go index c96e881..b62e564 100644 --- a/internals/proxy/middlewares/template.go +++ b/internals/proxy/middlewares/template.go @@ -195,7 +195,7 @@ func templateQuery(reqUrl *url.URL, VARIABLES interface{}) (string, map[string]i reqRawQuery := modifiedQuery.Encode() - if reqUrl.RawQuery != reqRawQuery { + if reqUrl.Query().Encode() != reqRawQuery { log.Debug("Applied Query Templating: ", templatedQuery) modified = true From 398c164e6a05d402b3e0bf761883591a29e6d2a3 Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Sat, 6 Sep 2025 19:28:17 +0200 Subject: [PATCH 16/18] debugging SESSION !!! --- internals/proxy/middlewares/template.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internals/proxy/middlewares/template.go b/internals/proxy/middlewares/template.go index b62e564..899b69b 100644 --- a/internals/proxy/middlewares/template.go +++ b/internals/proxy/middlewares/template.go @@ -159,6 +159,9 @@ func templatePath(reqUrl *url.URL, VARIABLES interface{}) (string, bool) { return reqUrl.Path, modified } + log.Debug("Path (before): ", reqUrl.Path) + log.Debug("Path (now): ", reqPath) + if reqUrl.Path != reqPath { log.Debug("Applied Path Templating: ", reqPath) From 51444a45f726ae60fc25aa7d1f202a915bc9e088 Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Sat, 6 Sep 2025 19:52:18 +0200 Subject: [PATCH 17/18] fixed query templating overwriting body --- internals/proxy/middlewares/template.go | 10 ++++------ utils/utils.go | 25 ++++++++++++------------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/internals/proxy/middlewares/template.go b/internals/proxy/middlewares/template.go index 899b69b..598165e 100644 --- a/internals/proxy/middlewares/template.go +++ b/internals/proxy/middlewares/template.go @@ -42,17 +42,17 @@ func (data TemplateMiddleware) Use() http.Handler { bodyData, modified = templateJSON(body.Data, VARIABLES) if modified { - modifiedBody = true + modifiedBody = true } } if req.URL.RawQuery != "" { var modified bool - req.URL.RawQuery, bodyData, modified = templateQuery(req.URL, VARIABLES) + req.URL.RawQuery, bodyData, modified = templateQuery(bodyData, req.URL, VARIABLES) if modified { - modifiedBody = true + modifiedBody = true } } @@ -171,11 +171,9 @@ func templatePath(reqUrl *url.URL, VARIABLES interface{}) (string, bool) { return reqPath, modified } -func templateQuery(reqUrl *url.URL, VARIABLES interface{}) (string, map[string]interface{}, bool) { +func templateQuery(data map[string]interface{}, reqUrl *url.URL, VARIABLES interface{}) (string, map[string]interface{}, bool) { var modified bool - data := map[string]interface{}{} - decodedQuery, _ := url.QueryUnescape(reqUrl.RawQuery) log.Debug("Decoded Query: ", decodedQuery) diff --git a/utils/utils.go b/utils/utils.go index c601461..181ff95 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -7,26 +7,25 @@ package utils import ( "encoding/json" - "regexp" "strings" ) func StringToArray(sliceStr string) []string { - if sliceStr == "" { - return nil - } - - re, err := regexp.Compile(`\s+`) - - if err != nil { - return nil - } + if sliceStr == "" { + return nil + } - normalized := re.ReplaceAllString(sliceStr, "") + rawItems := strings.Split(sliceStr, ",") + items := make([]string, 0, len(rawItems)) - tokens := strings.Split(normalized, ",") + for _, item := range rawItems { + trimmed := strings.TrimSpace(item) + if trimmed != "" { + items = append(items, trimmed) + } + } - return tokens + return items } func GetJsonSafe[T any](jsonStr string) (T, error) { From 1e1d14b78489714980bee819659ece9cc890a448 Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Sat, 6 Sep 2025 19:57:19 +0200 Subject: [PATCH 18/18] removed temp. debugs --- internals/proxy/middlewares/template.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/internals/proxy/middlewares/template.go b/internals/proxy/middlewares/template.go index 598165e..0bd5276 100644 --- a/internals/proxy/middlewares/template.go +++ b/internals/proxy/middlewares/template.go @@ -159,9 +159,6 @@ func templatePath(reqUrl *url.URL, VARIABLES interface{}) (string, bool) { return reqUrl.Path, modified } - log.Debug("Path (before): ", reqUrl.Path) - log.Debug("Path (now): ", reqPath) - if reqUrl.Path != reqPath { log.Debug("Applied Path Templating: ", reqPath)