Skip to content
Merged
35 changes: 18 additions & 17 deletions .github/templates/README.template.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,28 +230,29 @@ example:

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 extract the correct message from the Request Body.

Message Aliases can be added by setting `MESSAGE_ALIASES`:
| Alias | Score |
| ----------- | ----- |
| msg | 100 |
| content | 99 |
| description | 98 |
| text | 20 |
| body | 15 |
| summary | 10 |
| details | 9 |
| payload | 2 |
| data | 1 |

Secured Signal API will pick the best scoring Message Alias (if available) to extract the correct message from the Request Body.

Message Aliases can be added by setting `MESSAGE_ALIASES` to a valid json array containing dictionaries of `alias`, the json key to be used for lookup (use `.` dots for using values from a nested dictionary and `[i]` to get values from an array):

```yaml
environment:
MESSAGE_ALIASES: |
[
{ "alias": "note", "priority": 4 },
{ "alias": "test", "priority": 3 }
{ "alias": "msg", "score": 80 },
{ "alias": "data.message", "score": 79 },
{ "alias": "array[0].message", "score": 78 },
]
```

Expand Down
33 changes: 16 additions & 17 deletions internals/proxy/middlewares/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package middlewares
import (
"encoding/base64"
"net/http"
"net/url"
"slices"
"strings"

Expand Down Expand Up @@ -64,32 +63,32 @@ func (data AuthMiddleware) Use() http.Handler {
authToken := authBody[1]

switch authType {
case Bearer:
if isValidToken(tokens, authToken) {
success = true
}
case Bearer:
if isValidToken(tokens, authToken) {
success = true
}

case Basic:
basicAuthBody, err := base64.StdEncoding.DecodeString(authToken)
case Basic:
basicAuthBody, err := base64.StdEncoding.DecodeString(authToken)

if err != nil {
log.Error("Could not decode Basic Auth Payload: ", err.Error())
}
if err != nil {
log.Error("Could not decode Basic Auth Payload: ", err.Error())
}

basicAuth := string(basicAuthBody)
basicAuthParams := strings.Split(basicAuth, ":")
basicAuth := string(basicAuthBody)
basicAuthParams := strings.Split(basicAuth, ":")

user := "api"
user := "api"

if basicAuthParams[0] == user && isValidToken(tokens, basicAuthParams[1]) {
success = true
}
if basicAuthParams[0] == user && isValidToken(tokens, basicAuthParams[1]) {
success = true
}
}

} else if authQuery != "" {
authType = Query

authToken, _ := url.QueryUnescape(authQuery)
authToken := strings.TrimSpace(authQuery)

if isValidToken(tokens, authToken) {
success = true
Expand Down
28 changes: 20 additions & 8 deletions internals/proxy/middlewares/body.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import (
"net/http"
"strconv"

"github.com/codeshelldev/secured-signal-api/utils"
log "github.com/codeshelldev/secured-signal-api/utils/logger"
request "github.com/codeshelldev/secured-signal-api/utils/request"
)

type MessageAlias struct {
Alias string
Priority int
Score int
}

type BodyMiddleware struct {
Expand Down Expand Up @@ -74,17 +75,28 @@ func getMessage(aliases []MessageAlias, data map[string]interface{}) (string, ma
var best int

for _, alias := range aliases {
aliasKey := alias.Alias
priority := alias.Priority
aliasValue, score, ok := processAlias(alias, data)

value, ok := data[aliasKey]

if ok && value != "" && priority > best {
content = data[aliasKey].(string)
if ok && score > best {
content = aliasValue
}

data[aliasKey] = nil
data[alias.Alias] = nil
}

return content, data
}

func processAlias(alias MessageAlias, data map[string]interface{}) (string, int, bool) {
aliasKey := alias.Alias

value, ok := utils.GetJsonByPath(aliasKey, data)

aliasValue, isStr := value.(string)

if isStr && ok && aliasValue != "" {
return aliasValue, alias.Score, true
} else {
return "", 0, false
}
}
137 changes: 49 additions & 88 deletions internals/proxy/middlewares/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,16 @@ package middlewares

import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"text/template"

"github.com/codeshelldev/secured-signal-api/utils"
log "github.com/codeshelldev/secured-signal-api/utils/logger"
query "github.com/codeshelldev/secured-signal-api/utils/query"
"github.com/codeshelldev/secured-signal-api/utils/query"
request "github.com/codeshelldev/secured-signal-api/utils/request"
"github.com/codeshelldev/secured-signal-api/utils/templating"
)

type TemplateMiddleware struct {
Expand All @@ -39,7 +37,11 @@ func (data TemplateMiddleware) Use() http.Handler {
if !body.Empty {
var modified bool

bodyData, modified = templateJSON(body.Data, VARIABLES)
bodyData, modified, err = TemplateBody(body.Data, VARIABLES)

if err != nil {
log.Error("Error Templating JSON: ", err.Error())
}

if modified {
modifiedBody = true
Expand All @@ -49,7 +51,11 @@ func (data TemplateMiddleware) Use() http.Handler {
if req.URL.RawQuery != "" {
var modified bool

req.URL.RawQuery, bodyData, modified = templateQuery(bodyData, req.URL, VARIABLES)
req.URL.RawQuery, bodyData, modified, err = TemplateQuery(req.URL, bodyData, VARIABLES)

if err != nil {
log.Error("Error Templating Query: ", err.Error())
}

if modified {
modifiedBody = true
Expand All @@ -76,128 +82,83 @@ func (data TemplateMiddleware) Use() http.Handler {

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)

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{}, bool) {
var modified bool

for k, v := range data {
str, ok := v.(string)
if req.URL.Path != "" {
var modified bool

if ok {
re, err := regexp.Compile(`{{\s*\.([A-Za-z_][A-Za-z0-9_]*)\s*}}`)
req.URL.Path, modified, err = TemplatePath(req.URL, VARIABLES)

if err != nil {
log.Error("Error while Compiling Regex: ", err.Error())
log.Error("Error Templating Path: ", err.Error())
}

matches := re.FindAllStringSubmatch(str, -1)

if len(matches) > 1 {
for i, tmplStr := range matches {

tmplKey := matches[i][1]
if modified {
log.Debug("Applied Path Templating: ", req.URL.Path)
}
}

variable, err := json.Marshal(variables[tmplKey])
next.ServeHTTP(w, req)
})
}

if err != nil {
log.Error("Could not decode JSON: ", err.Error())
break
}
func TemplateBody(data map[string]interface{}, VARIABLES any) (map[string]interface{}, bool, error) {
var modified bool

data[k] = strings.ReplaceAll(str, string(variable), tmplStr[0])
}
templatedData, err := templating.RenderJSONTemplate("body", data, VARIABLES)

modified = true
} else if len(matches) == 1 {
tmplKey := matches[0][1]
if err != nil {
return data, false, err
}

data[k] = variables[tmplKey]
beforeStr := utils.ToJson(templatedData)
afterStr := utils.ToJson(data)

modified = true
}
}
}
modified = beforeStr == afterStr

return data, modified
return templatedData, modified, nil
}

func templatePath(reqUrl *url.URL, VARIABLES interface{}) (string, bool) {
func TemplatePath(reqUrl *url.URL, VARIABLES any) (string, bool, error) {
var modified bool

reqPath, err := url.PathUnescape(reqUrl.Path)

if err != nil {
log.Error("Error while Escaping Path: ", err.Error())
return reqUrl.Path, modified
return reqUrl.Path, modified, err
}

reqPath, err = renderTemplate("path", reqPath, VARIABLES)
reqPath, err = templating.RenderNormalizedTemplate("path", reqPath, VARIABLES)

if err != nil {
log.Error("Could not Template Path: ", err.Error())
return reqUrl.Path, modified
return reqUrl.Path, modified, err
}

if reqUrl.Path != reqPath {
log.Debug("Applied Path Templating: ", reqPath)

modified = true
}

return reqPath, modified
return reqPath, modified, nil
}

func templateQuery(data map[string]interface{}, reqUrl *url.URL, VARIABLES interface{}) (string, map[string]interface{}, bool) {
func TemplateQuery(reqUrl *url.URL, data map[string]interface{}, VARIABLES any) (string, map[string]interface{}, bool, error) {
var modified bool

decodedQuery, _ := url.QueryUnescape(reqUrl.RawQuery)

log.Debug("Decoded Query: ", decodedQuery)

templatedQuery, _ := renderTemplate("query", decodedQuery, VARIABLES)
templatedQuery, _ := templating.RenderNormalizedTemplate("query", decodedQuery, VARIABLES)

modifiedQuery := reqUrl.Query()
originalQueryData := reqUrl.Query()

queryData := query.ParseRawQuery(templatedQuery)
addedData := query.ParseTypedQuery(templatedQuery, "@")

for key, value := range queryData {
keyWithoutPrefix, found := strings.CutPrefix(key, "@")
for key, val := range addedData {
data[key] = val

if found {
data[keyWithoutPrefix] = query.ParseTypedQuery(value)

modifiedQuery.Del(key)
}
}

reqRawQuery := modifiedQuery.Encode()

if reqUrl.Query().Encode() != reqRawQuery {
log.Debug("Applied Query Templating: ", templatedQuery)
originalQueryData.Del(key)

modified = true
}

return reqRawQuery, data, modified
reqRawQuery := originalQueryData.Encode()

return reqRawQuery, data, modified, nil
}
Loading
Loading