diff --git a/.github/templates/README.template.md b/.github/templates/README.template.md index 84eff81..d27e16b 100644 --- a/.github/templates/README.template.md +++ b/.github/templates/README.template.md @@ -55,12 +55,12 @@ To use Basic Auth as Authorization Method add `Authorization: Basic BASE64_STRIN User is `api` (LOWERCASE) -Formatting for `BASE64_STRING` = `user:API_KEY`. +Formatting for `BASE64_STRING` = `user:API_TOKEN`. example: ```bash -echo "api:API_KEY" | base64 +echo "api:API_TOKEN" | base64 ``` => `YXBpOkFQSV9LRVkK` @@ -76,7 +76,7 @@ Here is a simple example: curl -X POST http://sec-signal-api:8880/v2/send?@authorization=API_TOKEN ``` -Notice the `@` infront of `authorization`. See [Formatting](#format) +Notice the `@` infront of `authorization`. See [Formatting](#format). ### Example @@ -121,9 +121,17 @@ http://sec-signal-api:8880/v1/receive/{{.NUMBER}} In some cases you may not be able to access / modify the Request Body, in that case specify needed values in the Request Query: -``` -http://sec-signal-api:8880/?@key=value -``` +Supported types include **strings**, **ints** and **arrays** + +`http://sec-signal-api:8880/?@key=value` + +| type | example | +| :--------- | :------ | +| string | abc | +| int | 123 | +| array | [1,2,3] | +| array(int) | 1,2,3 | +| array(str) | a,b,c | ##### Format @@ -132,14 +140,23 @@ you have to add `@` in front of any KeyValue Pair assignment. ### Environment Variables -#### API Token +#### API Token/s + +Both `API_TOKEN` and `API_TOKENS` support multiple Tokens seperated by **,**. +During Authentikcation Secured Signal API will try to match the given Token against the list of Tokens inside of these Variables. + +```yaml +environment: + API_TOKEN: "token1, token2, token3" + API_TOKENS: "token1, token2, token3" +``` > [!IMPORTANT] > It is highly recommended to set this Environment Variable > _What if I just don't?_ -Well, Secured Signal API will still work, but important Security Features won't be available +Secured Signal API will still work, but important Security Features won't be available like Blocked Endpoints and any sort of Auth. > [!NOTE] @@ -147,23 +164,18 @@ like Blocked Endpoints and any sort of Auth. #### Blocked Endpoints -Because Secured Signal API is just a secure Proxy you can use all of the [Signal REST API](https://github.com/bbernhard/signal-cli-rest-api/blob/master/doc/EXAMPLES.md) endpoints except for... - -- **/v1/about** - -- **/v1/configuration** - -- **/v1/devices** - -- **/v1/register** - -- **/v1/unregister** - -- **/v1/qrcodelink** - -- **/v1/accounts** - -- **/v1/contacts** +Because Secured Signal API is just a Proxy you can use all of the [Signal REST API](https://github.com/bbernhard/signal-cli-rest-api/blob/master/doc/EXAMPLES.md) endpoints except for... + +| Endpoint | +| :-------------------- | +| **/v1/about** | +| **/v1/configuration** | +| **/v1/devives** | +| **/v1/register** | +| **/v1/unregister** | +| **/v1/qrcodelink** | +| **/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 diff --git a/README.md b/README.md index ab2b0f7..9044c36 100644 --- a/README.md +++ b/README.md @@ -114,12 +114,12 @@ To use Basic Auth as Authorization Method add `Authorization: Basic BASE64_STRIN User is `api` (LOWERCASE) -Formatting for `BASE64_STRING` = `user:API_KEY`. +Formatting for `BASE64_STRING` = `user:API_TOKEN`. example: ```bash -echo "api:API_KEY" | base64 +echo "api:API_TOKEN" | base64 ``` => `YXBpOkFQSV9LRVkK` @@ -135,7 +135,7 @@ Here is a simple example: curl -X POST http://sec-signal-api:8880/v2/send?@authorization=API_TOKEN ``` -Notice the `@` infront of `authorization`. See [Formatting](#format) +Notice the `@` infront of `authorization`. See [Formatting](#format). ### Example @@ -180,9 +180,17 @@ http://sec-signal-api:8880/v1/receive/{{.NUMBER}} In some cases you may not be able to access / modify the Request Body, in that case specify needed values in the Request Query: -``` -http://sec-signal-api:8880/?@key=value -``` +Supported types include **strings**, **ints** and **arrays** + +`http://sec-signal-api:8880/?@key=value` + +| type | example | +| :--------- | :------ | +| string | abc | +| int | 123 | +| array | [1,2,3] | +| array(int) | 1,2,3 | +| array(str) | a,b,c | ##### Format @@ -191,14 +199,23 @@ you have to add `@` in front of any KeyValue Pair assignment. ### Environment Variables -#### API Token +#### API Token/s + +Both `API_TOKEN` and `API_TOKENS` support multiple Tokens seperated by **,**. +During Authentikcation Secured Signal API will try to match the given Token against the list of Tokens inside of these Variables. + +```yaml +environment: + API_TOKEN: "token1, token2, token3" + API_TOKENS: "token1, token2, token3" +``` > [!IMPORTANT] > It is highly recommended to set this Environment Variable > _What if I just don't?_ -Well, Secured Signal API will still work, but important Security Features won't be available +Secured Signal API will still work, but important Security Features won't be available like Blocked Endpoints and any sort of Auth. > [!NOTE] @@ -206,23 +223,18 @@ like Blocked Endpoints and any sort of Auth. #### Blocked Endpoints -Because Secured Signal API is just a secure Proxy you can use all of the [Signal REST API](https://github.com/bbernhard/signal-cli-rest-api/blob/master/doc/EXAMPLES.md) endpoints except for... - -- **/v1/about** - -- **/v1/configuration** - -- **/v1/devices** - -- **/v1/register** - -- **/v1/unregister** - -- **/v1/qrcodelink** - -- **/v1/accounts** - -- **/v1/contacts** +Because Secured Signal API is just a Proxy you can use all of the [Signal REST API](https://github.com/bbernhard/signal-cli-rest-api/blob/master/doc/EXAMPLES.md) endpoints except for... + +| Endpoint | +| :-------------------- | +| **/v1/about** | +| **/v1/configuration** | +| **/v1/devives** | +| **/v1/register** | +| **/v1/unregister** | +| **/v1/qrcodelink** | +| **/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 diff --git a/go.mod b/go.mod index 9a932b8..62c830e 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/codeshelldev/secured-signal-api -go 1.24.4 +go 1.24.5 require go.uber.org/zap v1.27.0 -require go.uber.org/multierr v1.10.0 // indirect +require go.uber.org/multierr v1.11.0 // indirect diff --git a/go.sum b/go.sum index e1a34f3..9fe9a49 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,14 @@ -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internals/proxy/middlewares/auth.go b/internals/proxy/middlewares/auth.go index aa00053..ba3417a 100644 --- a/internals/proxy/middlewares/auth.go +++ b/internals/proxy/middlewares/auth.go @@ -4,6 +4,7 @@ import ( "encoding/base64" "net/http" "net/url" + "slices" "strings" log "github.com/codeshelldev/secured-signal-api/utils/logger" @@ -11,7 +12,7 @@ import ( type AuthMiddleware struct { Next http.Handler - Token string + Tokens []string } type authType string @@ -34,12 +35,16 @@ func getAuthType(str string) authType { } } +func isValidToken(tokens []string, match string) (bool) { + return slices.Contains(tokens, match) +} + func (data AuthMiddleware) Use() http.Handler { next := data.Next - token := data.Token + tokens := data.Tokens return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if token == "" { + if len(tokens) <= 0 { next.ServeHTTP(w, req) return } @@ -60,7 +65,7 @@ func (data AuthMiddleware) Use() http.Handler { switch authType { case Bearer: - if authToken == token { + if isValidToken(tokens, authToken) { success = true } @@ -76,7 +81,7 @@ func (data AuthMiddleware) Use() http.Handler { user := "api" - if basicAuthParams[0] == user && basicAuthParams[1] == token { + if basicAuthParams[0] == user && isValidToken(tokens, basicAuthParams[1]) { success = true } } @@ -86,7 +91,7 @@ func (data AuthMiddleware) Use() http.Handler { authToken, _ := url.QueryUnescape(authQuery) - if authToken == token { + if isValidToken(tokens, authToken) { success = true modifiedQuery := req.URL.Query() diff --git a/main.go b/main.go index ce776c0..c430634 100644 --- a/main.go +++ b/main.go @@ -42,8 +42,8 @@ func main() { } auth_m1 := AuthMiddleware{ - Next: endp_m2.Use(), - Token: ENV.API_TOKEN, + Next: endp_m2.Use(), + Tokens: ENV.API_TOKENS, } log_m0 := LogMiddleware{ diff --git a/utils/env/env.go b/utils/env/env.go index 8350229..a25d9ea 100644 --- a/utils/env/env.go +++ b/utils/env/env.go @@ -1,17 +1,19 @@ package env import ( - "encoding/json" "os" + "strconv" + "strings" middlewares "github.com/codeshelldev/secured-signal-api/internals/proxy/middlewares" + "github.com/codeshelldev/secured-signal-api/utils" log "github.com/codeshelldev/secured-signal-api/utils/logger" ) type ENV_ struct { PORT string API_URL string - API_TOKEN string + API_TOKENS []string BLOCKED_ENDPOINTS []string VARIABLES map[string]any MESSAGE_ALIASES []middlewares.MessageAlias @@ -76,7 +78,11 @@ func Load() { ENV.PORT = os.Getenv("PORT") ENV.API_URL = os.Getenv("SIGNAL_API_URL") - ENV.API_TOKEN = os.Getenv("API_TOKEN") + apiToken := os.Getenv("API_TOKENS") + + if apiToken == "" { + apiToken = os.Getenv("API_TOKEN") + } blockedEndpointJSON := os.Getenv("BLOCKED_ENDPOINTS") recipientsJSON := os.Getenv("RECIPIENTS") @@ -85,59 +91,37 @@ func Load() { log.Info("Loaded Environment Variables") - if ENV.API_TOKEN == "" { + apiTokens, err := utils.StringToArray(apiToken) + + if err != nil { log.Warn("No API TOKEN provided this is NOT recommended") log.Info("Disabling Security Features due to incomplete Congfiguration") ENV.BLOCKED_ENDPOINTS = []string{} - } - - if blockedEndpointJSON != "" { - var blockedEndpoints []string + } else { + for _, token := range apiTokens { + log.Debug("Found Token: " + token[:2] + strings.Repeat("X", len(token) - 2)) + } - err := json.Unmarshal([]byte(blockedEndpointJSON), &blockedEndpoints) + log.Debug("Registered " + strconv.Itoa(len(apiTokens)) + " Tokens") - if err != nil { - log.Error("Could not decode Blocked Endpoints: ", blockedEndpointJSON) - } + ENV.API_TOKENS = apiTokens + } - ENV.BLOCKED_ENDPOINTS = blockedEndpoints + if blockedEndpointJSON != "" { + ENV.BLOCKED_ENDPOINTS = utils.GetJson[[]string](blockedEndpointJSON) } if messageAliasesJSON != "" { - var msgAliases []middlewares.MessageAlias - - err := json.Unmarshal([]byte(messageAliasesJSON), &msgAliases) - - if err != nil { - log.Error("Could not decode Message Aliases ", variablesJSON) - } - - ENV.MESSAGE_ALIASES = msgAliases + ENV.MESSAGE_ALIASES = utils.GetJson[[]middlewares.MessageAlias](messageAliasesJSON) } if variablesJSON != "" { - var variables map[string]interface{} - - err := json.Unmarshal([]byte(variablesJSON), &variables) - - if err != nil { - log.Error("Could not decode Variables ", variablesJSON) - } - - ENV.VARIABLES = variables + ENV.VARIABLES = utils.GetJson[map[string]any](variablesJSON) } if recipientsJSON != "" { - var recipients []string - - err := json.Unmarshal([]byte(recipientsJSON), &recipients) - - if err != nil { - log.Error("Could not decode Variables ", variablesJSON) - } - - ENV.VARIABLES["RECIPIENTS"] = recipients + ENV.VARIABLES["RECIPIENTS"] = utils.GetJson[[]string](recipientsJSON) } } \ No newline at end of file diff --git a/utils/logger/logger.go b/utils/logger/logger.go index 40dc904..7847ec2 100644 --- a/utils/logger/logger.go +++ b/utils/logger/logger.go @@ -48,16 +48,16 @@ func Init(level string) { func getLogLevel(level string) zapcore.Level { switch level { - case "info": - return zapcore.InfoLevel - case "debug": - return zapcore.DebugLevel - case "warn": - return zapcore.WarnLevel - case "error": - return zapcore.ErrorLevel - default: - return zapcore.InfoLevel + case "info": + return zapcore.InfoLevel + case "debug": + return zapcore.DebugLevel + case "warn": + return zapcore.WarnLevel + case "error": + return zapcore.ErrorLevel + default: + return zapcore.InfoLevel } } diff --git a/utils/utils.go b/utils/utils.go new file mode 100644 index 0000000..67368ff --- /dev/null +++ b/utils/utils.go @@ -0,0 +1,51 @@ +package utils + +/* + * General Functions (utils) + * Might move Functions into seperate files + */ + +import ( + "encoding/json" + "errors" + "regexp" + "strings" +) + +func StringToArray(sliceStr string) ([]string, error) { + if sliceStr == "" { + return []string{}, errors.New("sliceStr is empty") + } + + re, err := regexp.Compile(`\s+`) + + if err != nil { + return []string{}, err + } + + normalized := re.ReplaceAllString(sliceStr, "") + + tokens := strings.Split(normalized, ",") + + return tokens, nil +} + +func GetJsonSafe[T any](jsonStr string) (T, error) { + var result T + + err := json.Unmarshal([]byte(jsonStr), &result) + + return result, err +} + +func GetJson[T any](jsonStr string) (T) { + var result T + + err := json.Unmarshal([]byte(jsonStr), &result) + + if err != nil { + // JSON is empty + } + + return result +} \ No newline at end of file