Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 8 additions & 3 deletions .github/templates/README.template.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -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

Expand Down
81 changes: 41 additions & 40 deletions internals/proxy/middlewares/body.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ 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 {
Expand All @@ -25,65 +25,66 @@ 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)
body, err := request.GetReqBody(w, req)

if err != nil {
log.Error("Could not read Body: ", err.Error())
http.Error(w, "Bad Request", http.StatusBadRequest)
return
log.Error("Could not get Request Body: ", err.Error())
}
defer req.Body.Close()

if len(bodyBytes) > 0 {
var modifiedBody bool
var bodyData map[string]interface{}

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
}
172 changes: 109 additions & 63 deletions internals/proxy/middlewares/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,75 @@ 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 {
Next http.Handler
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) {
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 {
var modified bool

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

if modified {
modifiedBody = true
}
}

if req.URL.RawQuery != "" {
var modified bool

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

if modified {
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)

Expand All @@ -36,15 +98,17 @@ 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)

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())
log.Error("Error while Compiling Regex: ", err.Error())
}

matches := re.FindAllStringSubmatch(str, -1)
Expand All @@ -63,95 +127,77 @@ 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 (data TemplateMiddleware) Use() http.Handler {
next := data.Next
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
}
defer req.Body.Close()
func templatePath(reqUrl *url.URL, VARIABLES interface{}) (string, bool) {
var modified bool

if len(bodyBytes) > 0 {
reqPath, err := url.PathUnescape(reqUrl.Path)

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)
if err != nil {
log.Error("Error while Escaping Path: ", err.Error())
return reqUrl.Path, modified
}

log.Debug("Decoded Query: ", decodedQuery)
reqPath, err = renderTemplate("path", reqPath, VARIABLES)

templatedQuery, _ := renderTemplate("query", decodedQuery, VARIABLES)
if err != nil {
log.Error("Could not Template Path: ", err.Error())
return reqUrl.Path, modified
}

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

queryData := query.ParseRawQuery(templatedQuery)
modified = true
}

for key, value := range queryData {
keyWithoutPrefix, found := strings.CutPrefix(key, "@")
return reqPath, modified
}

if found {
modifiedBodyData[keyWithoutPrefix] = query.ParseTypedQuery(value)
func templateQuery(data map[string]interface{}, reqUrl *url.URL, VARIABLES interface{}) (string, map[string]interface{}, bool) {
var modified bool

modifiedQuery.Del(key)
}
}
decodedQuery, _ := url.QueryUnescape(reqUrl.RawQuery)

req.URL.RawQuery = modifiedQuery.Encode()
log.Debug("Decoded Query: ", decodedQuery)

log.Debug("Applied Query Templating: ", templatedQuery)
}
templatedQuery, _ := renderTemplate("query", decodedQuery, VARIABLES)

bodyBytes, err = json.Marshal(modifiedBodyData)
modifiedQuery := reqUrl.Query()

if err != nil {
log.Error("Could not encode Body: ", err.Error())
http.Error(w, "Internal Error", http.StatusInternalServerError)
return
}
queryData := query.ParseRawQuery(templatedQuery)

modifiedBody := string(bodyBytes)
for key, value := range queryData {
keyWithoutPrefix, found := strings.CutPrefix(key, "@")

log.Debug("Applied Body Templating: ", modifiedBody)
if found {
data[keyWithoutPrefix] = query.ParseTypedQuery(value)

req.ContentLength = int64(len(modifiedBody))
req.Header.Set("Content-Length", strconv.Itoa(len(modifiedBody)))
modifiedQuery.Del(key)
}
}

req.Body = io.NopCloser(bytes.NewReader(bodyBytes))

reqPath := req.URL.Path
reqPath, _ = url.PathUnescape(reqPath)
reqRawQuery := modifiedQuery.Encode()

modifiedReqPath, _ := renderTemplate("path", reqPath, VARIABLES)
if reqUrl.Query().Encode() != reqRawQuery {
log.Debug("Applied Query Templating: ", templatedQuery)

req.URL.Path = modifiedReqPath
modified = true
}

next.ServeHTTP(w, req)
})
}
return reqRawQuery, data, modified
}
Loading
Loading