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
25 changes: 25 additions & 0 deletions .github/templates/README.template.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,31 @@ environment:
DEFAULT_RECIPIENTS: ' [ "user.id", "000", "001", "group.id" ] '
```

#### Message Aliases

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

If you are missing a Message Alias to have Secured Signal API integrated with a Service you can simply set `MESSAGE_ALIASES` in ENV:

```yaml
environment:
MESSAGE_ALIASES: ' [{ "alias": "note", "priority": 4 }, { "alias": "test", "priority": 3 }] '
```

## Contributing

Found a bug? Want to change or add something?
Expand Down
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,31 @@ environment:
DEFAULT_RECIPIENTS: ' [ "user.id", "000", "001", "group.id" ] '
```

#### Message Aliases

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

If you are missing a Message Alias to have Secured Signal API integrated with a Service you can simply set `MESSAGE_ALIASES` in ENV:

```yaml
environment:
MESSAGE_ALIASES: ' [{ "alias": "note", "priority": 4 }, { "alias": "test", "priority": 3 }] '
```

## Contributing

Found a bug? Want to change or add something?
Expand Down
110 changes: 110 additions & 0 deletions internals/proxy/middlewares/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package middlewares

import (
"encoding/base64"
"net/http"
"net/url"
"strings"

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

type AuthMiddleware struct {
Next http.Handler
Token string
}

type authType string

const (
Bearer authType = "Bearer"
Basic authType = "Basic"
Query authType = "Query"
None authType = "None"
)

func getAuthType(str string) authType {
switch str {
case "Bearer":
return Bearer
case "Basic":
return Basic
default:
return None
}
}

func (data AuthMiddleware) Use() http.Handler {
next := data.Next
token := data.Token

return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if token == "" {
next.ServeHTTP(w, req)
return
}

authHeader := req.Header.Get("Authorization")

authQuery := req.URL.Query().Get("@authorization")

var authType authType = None

success := false

if authHeader != "" {
authBody := strings.Split(authHeader, " ")

authType = getAuthType(authBody[0])
authToken := authBody[1]

switch authType {
case Bearer:
if authToken == token {
success = true
}

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

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

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

user := "api"

if basicAuthParams[0] == user && basicAuthParams[1] == token {
success = true
}
}

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

authToken, _ := url.QueryUnescape(authQuery)

if authToken == token {
success = true

modifiedQuery := req.URL.Query()

modifiedQuery.Del("@authorization")

req.URL.RawQuery = modifiedQuery.Encode()
}
}

if !success {
w.Header().Set("WWW-Authenticate", "Basic realm=\"Login Required\", Bearer realm=\"Access Token Required\"")

log.Warn("User failed ", string(authType), " Auth")
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}

next.ServeHTTP(w, req)
})
}
89 changes: 89 additions & 0 deletions internals/proxy/middlewares/body.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package middlewares

import (
"bytes"
"encoding/json"
"io"
"net/http"
"strconv"

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

type MessageAlias struct {
Alias string
Priority int
}

type BodyMiddleware struct {
Next http.Handler
MessageAliases []MessageAlias
}

func (data BodyMiddleware) Use() http.Handler {
next := data.Next
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()

if len(bodyBytes) > 0 {

req.Body.Close()

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
}

content, ok := modifiedBodyData["message"]

if !ok || content == "" {
best := 0

for _, alias := range messageAliases {
aliasKey := alias.Alias
priority := alias.Priority

value, ok := modifiedBodyData[aliasKey]

if ok && value != "" && priority > best {
content = modifiedBodyData[aliasKey]
}

modifiedBodyData[aliasKey] = nil
}

modifiedBodyData["message"] = content

bodyBytes, err = json.Marshal(modifiedBodyData)

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

modifiedBody := string(bodyBytes)

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

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

next.ServeHTTP(w, req)
})
}
30 changes: 30 additions & 0 deletions internals/proxy/middlewares/endpoints.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package middlewares

import (
"net/http"
"slices"

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

type EndpointsMiddleware struct {
Next http.Handler
BlockedEndpoints []string
}

func (data EndpointsMiddleware) Use() http.Handler {
next := data.Next
BLOCKED_ENDPOINTS := data.BlockedEndpoints

return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
reqPath := req.URL.Path

if slices.Contains(BLOCKED_ENDPOINTS, reqPath) {
log.Warn("User tried to access blocked endpoint: ", reqPath)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}

next.ServeHTTP(w, req)
})
}
21 changes: 21 additions & 0 deletions internals/proxy/middlewares/log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package middlewares

import (
"net/http"

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

type LogMiddleware struct {
Next http.Handler
}

func (data LogMiddleware) Use() http.Handler {
next := data.Next

return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
log.Info(req.Method, " ", req.URL.Path, " ", req.URL.RawQuery)

next.ServeHTTP(w, req)
})
}
Loading