Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
ad5c8d3
loosen regex to allow for more comples go templates
CodeShellDev Sep 20, 2025
dfdfd34
update regex for json templating
CodeShellDev Sep 20, 2025
f87b937
fixed regex not matching
CodeShellDev Sep 20, 2025
4abbd8c
debug
CodeShellDev Sep 20, 2025
354c2e8
update regex
CodeShellDev Sep 20, 2025
58489b4
Update template.go regex
CodeShellDev Sep 20, 2025
bf9ad9f
update regex
CodeShellDev Sep 20, 2025
e5d0459
debugging templating char issue
CodeShellDev Sep 20, 2025
4154855
fixed missing dot in template normilization
CodeShellDev Sep 20, 2025
477fab9
debug
CodeShellDev Sep 20, 2025
b503215
added missing `+` in regex
CodeShellDev Sep 20, 2025
44e1380
readded missing debugs
CodeShellDev Sep 20, 2025
108c6c7
try unescaping string
CodeShellDev Sep 20, 2025
a2d3655
testing newline behaviour
CodeShellDev Sep 20, 2025
739eb6f
test new json templating func
CodeShellDev Sep 20, 2025
227c239
debugging new json templater
CodeShellDev Sep 20, 2025
68a60e8
further debugging
CodeShellDev Sep 20, 2025
739220c
testing templating
CodeShellDev Sep 20, 2025
c117da0
debugging
CodeShellDev Sep 20, 2025
febabe5
debugging empty message
CodeShellDev Sep 20, 2025
f617f0f
more testing
CodeShellDev Sep 20, 2025
81fe946
restrict regex to only {{.var}}
CodeShellDev Sep 20, 2025
557c709
fix
CodeShellDev Sep 20, 2025
35462e8
possible fix for nil message when no template provided
CodeShellDev Sep 20, 2025
003585d
debugging
CodeShellDev Sep 20, 2025
04deb7d
fixed modified flag
CodeShellDev Sep 20, 2025
1193a68
more debugging
CodeShellDev Sep 20, 2025
db4c7dd
fix message overwrite
CodeShellDev Sep 20, 2025
f16ebe9
added headers to templating with `#`
CodeShellDev Sep 21, 2025
d339302
remove unneeded funcs
CodeShellDev Sep 21, 2025
e1e439b
debugging template issues
CodeShellDev Sep 21, 2025
69cf270
testing
CodeShellDev Sep 21, 2025
4678bd2
fixed copy-order
CodeShellDev Sep 21, 2025
8ea9b95
dont combine headers with body
CodeShellDev Sep 21, 2025
db476a3
debugging leaftover `@`'s
CodeShellDev Sep 21, 2025
1ffaf80
use literal strings to prefix not illegal chars: `@` + `#`
CodeShellDev Sep 21, 2025
eb2132c
fix
CodeShellDev Sep 21, 2025
d4deecf
debugging
CodeShellDev Sep 21, 2025
7940342
fix
CodeShellDev Sep 21, 2025
9899092
fixed body_key_ persisting after templating
CodeShellDev Sep 21, 2025
60ee50f
redact auth header
CodeShellDev Sep 21, 2025
c5eff62
implemented cleaning
CodeShellDev Sep 21, 2025
5fba451
convert dashes to underscores
CodeShellDev Sep 21, 2025
94a2d50
actually do not convert, let user handle
CodeShellDev Sep 21, 2025
17093e9
convert - into _ in headers
CodeShellDev Sep 21, 2025
d127ef4
debug array conversion error
CodeShellDev Sep 21, 2025
26d55d8
fixed regex order
CodeShellDev Sep 21, 2025
905bb73
check headers
CodeShellDev Sep 21, 2025
0758f88
removed due to expected behaviour
CodeShellDev Sep 21, 2025
268fd8b
fix auth header appearing as array
CodeShellDev Sep 21, 2025
a68a26e
fix header
CodeShellDev Sep 21, 2025
2e056d6
add space between auth header
CodeShellDev Sep 21, 2025
9ff0b2b
update README
CodeShellDev Sep 21, 2025
8814a9e
add highlighting to readme
CodeShellDev Sep 21, 2025
24569d1
Merge branch 'main' into feat/improve-templating
CodeShellDev Sep 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 52 additions & 2 deletions .github/templates/README.template.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,15 @@ curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer API_T

If you are not comfortable / don't want to hardcode your Number for example and/or Recipients in you, may use **Placeholders** in your Request.

You can use [**Variable**](#variables) `{{.NUMBER}}` Placeholders and **Body** Placeholders `{{@data.key}}`.
**How to use:**

| Type | Example | Note |
| :--------------------- | :------------------ | :--------------- |
| Body | `{{@data.key}}` | |
| Header | `{{#Content_Type}}` | `-` becomes `_` |
| [Variable](#variables) | `{{.VAR}}` | always uppercase |

**Where to use:**

| Type | Example |
| :---- | :--------------------------------------------------------------- |
Expand Down Expand Up @@ -220,6 +228,47 @@ If you are using Environment Variables as an example you won't be able to specif
> If you have a string that should not be turned into any other type, then you will need to escape all Type Denotations, `[]` or `{}` (also `-`) with a `\` **Backslash** (or Double Backslash).
> An **Odd** number of **Backslashes** **escape** the character in front of them and an **Even** number leave the character **as-is**.

### Templating

Secured Signal API uses Golang's [Standard Templating Library](https://pkg.go.dev/text/template).
This means that any valid Go template string will also work in Secured Signal API.

Go's templating library is used in the following features:

- [Message Templates](#message-templates)
- [Placeholders](#placeholders)

This makes advanced [Message Templates](#message-templates) like this one possible:

```yaml
settings:
messageTemplate: |
{{- $greeting := "Hello" -}}
{{ $greeting }}, {{ @name }}!
{{ if @age -}}
You are {{ @age }} years old.
{{- else -}}
Age unknown.
{{- end }}
Your friends:
{{- range @friends }}
- {{ . }}
{{- else }}
You have no friends.
{{- end }}
Profile details:
{{- range $key, $value := @profile }}
- {{ $key }}: {{ $value }}
{{- end }}
{{ define "footer" -}}
This is the footer for {{ @name }}.
{{- end }}
{{ template "footer" . -}}
------------------------------------
Content-Type: {{ #Content_Type }}
Redacted Auth Header: {{ #Authorization }}
```

### API Token(s)

During Authentication Secured Signal API will try to match the given Token against the list of Tokens inside of these Variables.
Expand Down Expand Up @@ -301,7 +350,8 @@ settings:
Sent with Secured Signal API.
```

Use `{{@data.key}}` to reference Body Keys and `{{.KEY}}` for Variables.
Message Templates support [Standard Golang Templating](#templating).
Use `@data.key` to reference Body Keys, `#Content_Type` for Headers and `.KEY` for Variables.

### Data Aliases

Expand Down
12 changes: 7 additions & 5 deletions internals/proxy/middlewares/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ func (data MessageMiddleware) Use() http.Handler {
bodyData = body.Data

if messageTemplate != "" {
newData, err := TemplateMessage(messageTemplate, bodyData, variables)
headerData := request.GetReqHeaders(req)

newData, err := TemplateMessage(messageTemplate, bodyData, headerData, variables)

if err != nil {
log.Error("Error Templating Message: ", err.Error())
Expand Down Expand Up @@ -83,13 +85,13 @@ func (data MessageMiddleware) Use() http.Handler {
})
}

func TemplateMessage(template string, data map[string]any, VARIABLES map[string]any) (map[string]any, error) {
data["message_template"] = template
func TemplateMessage(template string, bodyData map[string]any, headerData map[string]any, variables map[string]any) (map[string]any, error) {
bodyData["message_template"] = template

data, _, err := TemplateBody(data, VARIABLES)
data, _, err := TemplateBody(bodyData, headerData, variables)

if err != nil || data == nil {
return data, err
return bodyData, err
}

data["message"] = data["message_template"]
Expand Down
96 changes: 83 additions & 13 deletions internals/proxy/middlewares/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package middlewares
import (
"bytes"
"io"
"maps"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"

jsonutils "github.com/codeshelldev/secured-signal-api/utils/jsonutils"
log "github.com/codeshelldev/secured-signal-api/utils/logger"
Expand Down Expand Up @@ -42,7 +44,9 @@ func (data TemplateMiddleware) Use() http.Handler {
if !body.Empty {
var modified bool

bodyData, modified, err = TemplateBody(body.Data, variables)
headerData := request.GetReqHeaders(req)

bodyData, modified, err = TemplateBody(body.Data, headerData, variables)

if err != nil {
log.Error("Error Templating JSON: ", err.Error())
Expand Down Expand Up @@ -105,19 +109,23 @@ func (data TemplateMiddleware) Use() http.Handler {
})
}

func TemplateBody(data map[string]any, VARIABLES map[string]any) (map[string]any, bool, error) {
var modified bool

func normalizeData(fromPrefix, toPrefix string, data map[string]any) (map[string]any, error) {
jsonStr := jsonutils.ToJson(data)

if jsonStr != "" {
re, err := regexp.Compile(`{{\s*\@([a-zA-Z0-9_.]+)\s*}}`)
toVar, err := templating.TransformTemplateKeys(jsonStr, fromPrefix, func(re *regexp.Regexp, match string) string {
return re.ReplaceAllStringFunc(match, func(varMatch string) string {
varName := re.ReplaceAllString(varMatch, "$1")

return "." + toPrefix + varName
})
})

if err != nil {
return data, false, err
return data, err
}

jsonStr = re.ReplaceAllString(jsonStr, "{{.$1}}")
jsonStr = toVar

normalizedData, err := jsonutils.GetJsonSafe[map[string]any](jsonStr)

Expand All @@ -126,16 +134,78 @@ func TemplateBody(data map[string]any, VARIABLES map[string]any) (map[string]any
}
}

templatedData, err := templating.RenderJSON("body", data, VARIABLES)
return data, nil
}

func prefixData(prefix string, data map[string]any) (map[string]any) {
res := map[string]any{}

for key, value := range data {
res[prefix + key] = value
}

return res
}

func cleanHeaders(headers map[string]any) map[string]any {
cleanedHeaders := map[string]any{}

for key, value := range headers {
cleanedKey := strings.ReplaceAll(key, "-", "_")

cleanedHeaders[cleanedKey] = value
}

authHeader, ok := cleanedHeaders["Authorization"].([]string)

if !ok {
authHeader = []string{"UNKNOWN REDACTED"}
}

cleanedHeaders["Authorization"] = strings.Split(authHeader[0], ` `)[0] + " REDACTED"

return cleanedHeaders
}

func TemplateBody(body map[string]any, headers map[string]any, VARIABLES map[string]any) (map[string]any, bool, error) {
var modified bool

headers = cleanHeaders(headers)

// Normalize #Var and @Var to .header_key_Var and .body_key_Var
normalizedBody, err := normalizeData("@", "body_key_", body)

if err != nil {
return body, false, err
}

normalizedBody, err = normalizeData("#", "header_key_", normalizedBody)

if err != nil {
return data, false, err
return body, false, err
}

beforeStr := jsonutils.ToJson(templatedData)
afterStr := jsonutils.ToJson(data)
// Prefix Body Data with body_key_
prefixedBody := prefixData("body_key_", normalizedBody)

// Prefix Header Data with header_key_
prefixedHeaders := prefixData("header_key_", headers)

variables := VARIABLES

maps.Copy(variables, prefixedBody)
maps.Copy(variables, prefixedHeaders)

modified = beforeStr == afterStr
templatedData, err := templating.RenderJSON("body", normalizedBody, variables)

if err != nil {
return body, false, err
}

beforeStr := jsonutils.ToJson(body)
afterStr := jsonutils.ToJson(templatedData)

modified = beforeStr != afterStr

return templatedData, modified, nil
}
Expand Down Expand Up @@ -184,4 +254,4 @@ func TemplateQuery(reqUrl *url.URL, data map[string]any, VARIABLES any) (string,
reqRawQuery := originalQueryData.Encode()

return reqRawQuery, data, modified, nil
}
}
4 changes: 2 additions & 2 deletions tests/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ func TestJsonTemplating(t *testing.T) {
"key2": 4,
}

got, err := templating.RenderJSONTemplate("json", data, variables)
got, err := templating.RenderDataKeyTemplateRecursive("", data, variables)

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

expectedStr := jsonutils.ToJson(expected)
Expand Down
10 changes: 10 additions & 0 deletions utils/request/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,16 @@ func GetBody(req *http.Request) ([]byte, error) {
return bodyBytes, nil
}

func GetReqHeaders(req *http.Request) (map[string]any) {
data := map[string]any{}

for key, value := range req.Header {
data[key] = value
}

return data
}

func GetReqBody(w http.ResponseWriter, req *http.Request) (Body, error) {
bytes, err := GetBody(req)

Expand Down
2 changes: 1 addition & 1 deletion utils/stringutils/stringutils.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package safestrings
package stringutils

import (
"regexp"
Expand Down
Loading