Skip to content

Commit 042ab7c

Browse files
authored
Merge pull request #35 from CodeShellDev/dev
- added Multi-Level Message Aliases (`dict.key`) - added go tests - improved and cleaned up templating - renamed Priority to Score
2 parents 68ea8fa + fd8873c commit 042ab7c

File tree

11 files changed

+486
-141
lines changed

11 files changed

+486
-141
lines changed

.github/templates/README.template.md

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -230,28 +230,29 @@ example:
230230

231231
To improve compatibility with other services Secured Signal API provides aliases for the `message` attribute by default:
232232

233-
| Alias | Priority |
234-
| ----------- | -------- |
235-
| msg | 100 |
236-
| content | 99 |
237-
| description | 98 |
238-
| text | 20 |
239-
| body | 15 |
240-
| summary | 10 |
241-
| details | 9 |
242-
| payload | 2 |
243-
| data | 1 |
244-
245-
Secured Signal API will use the highest priority Message Alias to extract the correct message from the Request Body.
246-
247-
Message Aliases can be added by setting `MESSAGE_ALIASES`:
233+
| Alias | Score |
234+
| ----------- | ----- |
235+
| msg | 100 |
236+
| content | 99 |
237+
| description | 98 |
238+
| text | 20 |
239+
| body | 15 |
240+
| summary | 10 |
241+
| details | 9 |
242+
| payload | 2 |
243+
| data | 1 |
244+
245+
Secured Signal API will pick the best scoring Message Alias (if available) to extract the correct message from the Request Body.
246+
247+
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):
248248

249249
```yaml
250250
environment:
251251
MESSAGE_ALIASES: |
252252
[
253-
{ "alias": "note", "priority": 4 },
254-
{ "alias": "test", "priority": 3 }
253+
{ "alias": "msg", "score": 80 },
254+
{ "alias": "data.message", "score": 79 },
255+
{ "alias": "array[0].message", "score": 78 },
255256
]
256257
```
257258

internals/proxy/middlewares/auth.go

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package middlewares
33
import (
44
"encoding/base64"
55
"net/http"
6-
"net/url"
76
"slices"
87
"strings"
98

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

6665
switch authType {
67-
case Bearer:
68-
if isValidToken(tokens, authToken) {
69-
success = true
70-
}
66+
case Bearer:
67+
if isValidToken(tokens, authToken) {
68+
success = true
69+
}
7170

72-
case Basic:
73-
basicAuthBody, err := base64.StdEncoding.DecodeString(authToken)
71+
case Basic:
72+
basicAuthBody, err := base64.StdEncoding.DecodeString(authToken)
7473

75-
if err != nil {
76-
log.Error("Could not decode Basic Auth Payload: ", err.Error())
77-
}
74+
if err != nil {
75+
log.Error("Could not decode Basic Auth Payload: ", err.Error())
76+
}
7877

79-
basicAuth := string(basicAuthBody)
80-
basicAuthParams := strings.Split(basicAuth, ":")
78+
basicAuth := string(basicAuthBody)
79+
basicAuthParams := strings.Split(basicAuth, ":")
8180

82-
user := "api"
81+
user := "api"
8382

84-
if basicAuthParams[0] == user && isValidToken(tokens, basicAuthParams[1]) {
85-
success = true
86-
}
83+
if basicAuthParams[0] == user && isValidToken(tokens, basicAuthParams[1]) {
84+
success = true
85+
}
8786
}
8887

8988
} else if authQuery != "" {
9089
authType = Query
9190

92-
authToken, _ := url.QueryUnescape(authQuery)
91+
authToken := strings.TrimSpace(authQuery)
9392

9493
if isValidToken(tokens, authToken) {
9594
success = true

internals/proxy/middlewares/body.go

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ import (
66
"net/http"
77
"strconv"
88

9+
"github.com/codeshelldev/secured-signal-api/utils"
910
log "github.com/codeshelldev/secured-signal-api/utils/logger"
1011
request "github.com/codeshelldev/secured-signal-api/utils/request"
1112
)
1213

1314
type MessageAlias struct {
1415
Alias string
15-
Priority int
16+
Score int
1617
}
1718

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

7677
for _, alias := range aliases {
77-
aliasKey := alias.Alias
78-
priority := alias.Priority
78+
aliasValue, score, ok := processAlias(alias, data)
7979

80-
value, ok := data[aliasKey]
81-
82-
if ok && value != "" && priority > best {
83-
content = data[aliasKey].(string)
80+
if ok && score > best {
81+
content = aliasValue
8482
}
8583

86-
data[aliasKey] = nil
84+
data[alias.Alias] = nil
8785
}
8886

8987
return content, data
88+
}
89+
90+
func processAlias(alias MessageAlias, data map[string]interface{}) (string, int, bool) {
91+
aliasKey := alias.Alias
92+
93+
value, ok := utils.GetJsonByPath(aliasKey, data)
94+
95+
aliasValue, isStr := value.(string)
96+
97+
if isStr && ok && aliasValue != "" {
98+
return aliasValue, alias.Score, true
99+
} else {
100+
return "", 0, false
101+
}
90102
}

internals/proxy/middlewares/template.go

Lines changed: 49 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,16 @@ package middlewares
22

33
import (
44
"bytes"
5-
"encoding/json"
65
"io"
76
"net/http"
87
"net/url"
9-
"regexp"
108
"strconv"
11-
"strings"
12-
"text/template"
139

10+
"github.com/codeshelldev/secured-signal-api/utils"
1411
log "github.com/codeshelldev/secured-signal-api/utils/logger"
15-
query "github.com/codeshelldev/secured-signal-api/utils/query"
12+
"github.com/codeshelldev/secured-signal-api/utils/query"
1613
request "github.com/codeshelldev/secured-signal-api/utils/request"
14+
"github.com/codeshelldev/secured-signal-api/utils/templating"
1715
)
1816

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

42-
bodyData, modified = templateJSON(body.Data, VARIABLES)
40+
bodyData, modified, err = TemplateBody(body.Data, VARIABLES)
41+
42+
if err != nil {
43+
log.Error("Error Templating JSON: ", err.Error())
44+
}
4345

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

52-
req.URL.RawQuery, bodyData, modified = templateQuery(bodyData, req.URL, VARIABLES)
54+
req.URL.RawQuery, bodyData, modified, err = TemplateQuery(req.URL, bodyData, VARIABLES)
55+
56+
if err != nil {
57+
log.Error("Error Templating Query: ", err.Error())
58+
}
5359

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

7783
req.Body = io.NopCloser(bytes.NewReader(body.Raw))
7884

79-
req.URL.Path, _ = templatePath(req.URL, VARIABLES)
80-
81-
next.ServeHTTP(w, req)
82-
})
83-
}
84-
85-
func renderTemplate(name string, tmplStr string, data any) (string, error) {
86-
tmpl, err := template.New(name).Parse(tmplStr)
87-
88-
if err != nil {
89-
return "", err
90-
}
91-
var buf bytes.Buffer
92-
93-
err = tmpl.Execute(&buf, data)
94-
95-
if err != nil {
96-
return "", err
97-
}
98-
return buf.String(), nil
99-
}
100-
101-
func templateJSON(data map[string]interface{}, variables map[string]interface{}) (map[string]interface{}, bool) {
102-
var modified bool
103-
104-
for k, v := range data {
105-
str, ok := v.(string)
85+
if req.URL.Path != "" {
86+
var modified bool
10687

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

11090
if err != nil {
111-
log.Error("Error while Compiling Regex: ", err.Error())
91+
log.Error("Error Templating Path: ", err.Error())
11292
}
11393

114-
matches := re.FindAllStringSubmatch(str, -1)
115-
116-
if len(matches) > 1 {
117-
for i, tmplStr := range matches {
118-
119-
tmplKey := matches[i][1]
94+
if modified {
95+
log.Debug("Applied Path Templating: ", req.URL.Path)
96+
}
97+
}
12098

121-
variable, err := json.Marshal(variables[tmplKey])
99+
next.ServeHTTP(w, req)
100+
})
101+
}
122102

123-
if err != nil {
124-
log.Error("Could not decode JSON: ", err.Error())
125-
break
126-
}
103+
func TemplateBody(data map[string]interface{}, VARIABLES any) (map[string]interface{}, bool, error) {
104+
var modified bool
127105

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

131-
modified = true
132-
} else if len(matches) == 1 {
133-
tmplKey := matches[0][1]
108+
if err != nil {
109+
return data, false, err
110+
}
134111

135-
data[k] = variables[tmplKey]
112+
beforeStr := utils.ToJson(templatedData)
113+
afterStr := utils.ToJson(data)
136114

137-
modified = true
138-
}
139-
}
140-
}
115+
modified = beforeStr == afterStr
141116

142-
return data, modified
117+
return templatedData, modified, nil
143118
}
144119

145-
func templatePath(reqUrl *url.URL, VARIABLES interface{}) (string, bool) {
120+
func TemplatePath(reqUrl *url.URL, VARIABLES any) (string, bool, error) {
146121
var modified bool
147122

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

150125
if err != nil {
151-
log.Error("Error while Escaping Path: ", err.Error())
152-
return reqUrl.Path, modified
126+
return reqUrl.Path, modified, err
153127
}
154128

155-
reqPath, err = renderTemplate("path", reqPath, VARIABLES)
129+
reqPath, err = templating.RenderNormalizedTemplate("path", reqPath, VARIABLES)
156130

157131
if err != nil {
158-
log.Error("Could not Template Path: ", err.Error())
159-
return reqUrl.Path, modified
132+
return reqUrl.Path, modified, err
160133
}
161134

162135
if reqUrl.Path != reqPath {
163-
log.Debug("Applied Path Templating: ", reqPath)
164-
165136
modified = true
166137
}
167138

168-
return reqPath, modified
139+
return reqPath, modified, nil
169140
}
170141

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

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

176-
log.Debug("Decoded Query: ", decodedQuery)
177-
178-
templatedQuery, _ := renderTemplate("query", decodedQuery, VARIABLES)
147+
templatedQuery, _ := templating.RenderNormalizedTemplate("query", decodedQuery, VARIABLES)
179148

180-
modifiedQuery := reqUrl.Query()
149+
originalQueryData := reqUrl.Query()
181150

182-
queryData := query.ParseRawQuery(templatedQuery)
151+
addedData := query.ParseTypedQuery(templatedQuery, "@")
183152

184-
for key, value := range queryData {
185-
keyWithoutPrefix, found := strings.CutPrefix(key, "@")
153+
for key, val := range addedData {
154+
data[key] = val
186155

187-
if found {
188-
data[keyWithoutPrefix] = query.ParseTypedQuery(value)
189-
190-
modifiedQuery.Del(key)
191-
}
192-
}
193-
194-
reqRawQuery := modifiedQuery.Encode()
195-
196-
if reqUrl.Query().Encode() != reqRawQuery {
197-
log.Debug("Applied Query Templating: ", templatedQuery)
156+
originalQueryData.Del(key)
198157

199158
modified = true
200159
}
201160

202-
return reqRawQuery, data, modified
161+
reqRawQuery := originalQueryData.Encode()
162+
163+
return reqRawQuery, data, modified, nil
203164
}

0 commit comments

Comments
 (0)