Skip to content

Commit

Permalink
screener auth fix/gindump middleware (#2782)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Trajan0x <[email protected]>
  • Loading branch information
golangisfun123 and trajan0x authored Jun 25, 2024
1 parent 239a996 commit 9d19326
Show file tree
Hide file tree
Showing 8 changed files with 710 additions and 61 deletions.
11 changes: 5 additions & 6 deletions contrib/screener-api/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,18 +92,17 @@ func (c clientImpl) BlacklistAddress(ctx context.Context, appsecret string, appi
}

message := fmt.Sprintf("%s%s%s%s%s%s%s",
appid, timestamp, nonce, "POST", "/api/data/sync/", queryString, string(bodyBz))
appid, timestamp, nonce, "POST", "/api/data/sync", queryString, string(bodyBz))

signature := GenerateSignature(appsecret, message)

resp, err := c.rClient.R().
SetContext(ctx).
SetHeader("Content-Type", "application/json").
SetHeader("AppID", appid).
SetHeader("Timestamp", timestamp).
SetHeader("Nonce", nonce).
SetHeader("QueryString", queryString).
SetHeader("Signature", signature).
SetHeader("X-Signature-appid", appid).
SetHeader("X-Signature-timestamp", timestamp).
SetHeader("X-Signature-nonce", nonce).
SetHeader("X-Signature-signature", signature).
SetBody(body).
SetResult(&blacklistRes).
Post("/api/data/sync/")
Expand Down
1 change: 1 addition & 0 deletions contrib/screener-api/screener/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package screener
import (
"context"
"fmt"

"github.com/synapsecns/sanguine/contrib/screener-api/config"
"github.com/synapsecns/sanguine/contrib/screener-api/trmlabs"
"github.com/synapsecns/sanguine/core/metrics"
Expand Down
53 changes: 30 additions & 23 deletions contrib/screener-api/screener/screener.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package screener

import (
"bytes"
"context"
"encoding/json"
"errors"
Expand Down Expand Up @@ -94,7 +95,7 @@ func NewScreener(ctx context.Context, cfg config.Config, metricHandler metrics.H
screener.router.Use(screener.metrics.Gin())
screener.router.Handle(http.MethodGet, "/:ruleset/address/:address", screener.screenAddress)

screener.router.Handle(http.MethodPost, "/api/data/sync", screener.authMiddleware(cfg), screener.blacklistAddress)
screener.router.Handle(http.MethodPost, "/api/data/sync", ginhelper.TraceMiddleware(metricHandler.Tracer(), true), screener.authMiddleware(cfg), screener.blacklistAddress)
screener.router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))

return &screener, nil
Expand Down Expand Up @@ -201,7 +202,7 @@ func (s *screenerImpl) blacklistAddress(c *gin.Context) {
return

case "delete":
if err := s.db.DeleteBlacklistedAddress(ctx, blacklistedAddress.Address); err != nil {
if err := s.db.DeleteBlacklistedAddress(ctx, blacklistedAddress.ID); err != nil {
span.AddEvent("error", trace.WithAttributes(attribute.String("error", err.Error())))
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
Expand All @@ -220,47 +221,53 @@ func (s *screenerImpl) blacklistAddress(c *gin.Context) {

// This function takes the HTTP headers and the body of the request and reconstructs the signature to
// compare it with the signature provided. If they match, the request is allowed to pass through.
// nolint: canonicalheader
func (s *screenerImpl) authMiddleware(cfg config.Config) gin.HandlerFunc {
return func(c *gin.Context) {
_, span := s.metrics.Tracer().Start(c.Request.Context(), "authMiddleware")
defer span.End()

appID := c.Request.Header.Get("AppID")
timestamp := c.Request.Header.Get("Timestamp")
nonce := c.Request.Header.Get("Nonce")
signature := c.Request.Header.Get("Signature")
queryString := c.Request.Header.Get("QueryString")
bodyBytes, _ := io.ReadAll(c.Request.Body)
appID := c.Request.Header.Get("X-Signature-appid")
timestamp := c.Request.Header.Get("X-Signature-timestamp")
nonce := c.Request.Header.Get("X-Signature-nonce")
signature := c.Request.Header.Get("X-Signature-signature")
queryString := c.Request.URL.RawQuery

bodyBytes, err := io.ReadAll(c.Request.Body)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "could not read request body"})
c.Abort()
return
}
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
bodyStr := string(bodyBytes)

c.Request.Body = io.NopCloser(strings.NewReader(bodyStr))
message := fmt.Sprintf("%s%s%s%s%s%s%s",
appID, timestamp, nonce, "POST", "/api/data/sync", queryString, bodyStr)

expectedSignature := client.GenerateSignature(cfg.AppSecret, message)

span.SetAttributes(
attribute.String("appId", appID),
attribute.String("appid", appID),
attribute.String("timestamp", timestamp),
attribute.String("nonce", nonce),
attribute.String("signature", signature),
attribute.String("queryString", queryString),
attribute.String("bodyString", bodyStr),
attribute.String("body", bodyStr),
attribute.String("expectedSignature", expectedSignature),
attribute.String("message", message),
)

message := fmt.Sprintf("%s%s%s%s%s%s%s",
appID, timestamp, nonce, "POST", "/api/data/sync/", queryString, bodyStr)

span.AddEvent("message", trace.WithAttributes(attribute.String("message", message)))

expectedSignature := client.GenerateSignature(cfg.AppSecret, message)

span.AddEvent("generated_signature", trace.WithAttributes(attribute.String("expectedSignature", expectedSignature)))

if expectedSignature != signature {
span.AddEvent("error", trace.WithAttributes(attribute.String("error", "Invalid signature")))
span.AddEvent(
"error",
trace.WithAttributes(attribute.String("error", "Invalid signature"+expectedSignature)),
)
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid signature"})
c.Abort()
return
}

span.AddEvent("signature_validated")
span.AddEvent("success", trace.WithAttributes(attribute.String("message", "Valid signature"+expectedSignature)))
c.Next()
}
}
Expand Down
112 changes: 80 additions & 32 deletions contrib/screener-api/screener/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package screener_test

import (
"context"
"crypto/rand"
"encoding/json"
"errors"
"fmt"
"math/big"
"strconv"
"testing"
"time"
Expand Down Expand Up @@ -154,47 +157,81 @@ func (s *ScreenerSuite) TestScreener() {
False(s.T(), out)

// now test crud screener
blacklistBody := client.BlackListBody{
Type: "create",
ID: "1",
Data: "{\"test\":\"data\"}",
Address: "0x123",
Network: "eth",
Tag: "tag",
Remark: "remark",
}

// post to the blacklist
status, err := apiClient.BlacklistAddress(s.GetTestContext(), cfg.AppSecret, cfg.AppID, blacklistBody)
fmt.Println(status)
Equal(s.T(), "success", status)
// create a bunch
statuses, err := blacklistTestWithOperation(s.T(), "create", apiClient, cfg)
Equal(s.T(), len(statuses), 10)
all(s.T(), statuses, func(status string) bool {
return status == success
})
Nil(s.T(), err)

// update an address on the blacklist
blacklistBody.Type = "update"
blacklistBody.Remark = "new remark"

status, err = apiClient.BlacklistAddress(s.GetTestContext(), cfg.AppSecret, cfg.AppID, blacklistBody)
fmt.Println(status)
Equal(s.T(), "success", status)
// update a bunch
statuses, err = blacklistTestWithOperation(s.T(), "update", apiClient, cfg)
Equal(s.T(), len(statuses), 10)
all(s.T(), statuses, func(status string) bool {
return status == success
})
Nil(s.T(), err)

// delete the address on the blacklist
blacklistBody.Type = "delete"
blacklistBody.ID = "1"

status, err = apiClient.BlacklistAddress(s.GetTestContext(), cfg.AppSecret, cfg.AppID, blacklistBody)
fmt.Println(status)
Equal(s.T(), "success", status)
// delete a bunch
statuses, err = blacklistTestWithOperation(s.T(), "delete", apiClient, cfg)
Equal(s.T(), len(statuses), 10)
all(s.T(), statuses, func(status string) bool {
return status == success
})
Nil(s.T(), err)

// unauthorized
status, err = apiClient.BlacklistAddress(s.GetTestContext(), "bad", cfg.AppID, blacklistBody)
fmt.Println(status)
NotEqual(s.T(), "success", status)
// unauthorized, return on err so statuses will be only one
cfg.AppSecret = "BAD"
statuses, err = blacklistTestWithOperation(s.T(), "create", apiClient, cfg)
all(s.T(), statuses, func(status string) bool {
return status == "401 Unauthorized"
})
Equal(s.T(), len(statuses), 1)
NotNil(s.T(), err)
}

func blacklistTestWithOperation(t *testing.T, operation string, apiClient client.ScreenerClient, cfg config.Config) (statuses []string, err error) {
t.Helper()
for range 10 {
randomNumber, err := rand.Int(rand.Reader, big.NewInt(1000))
if err != nil {
return statuses, fmt.Errorf("error generating random number: %w", err)
}

dataMap := map[string]string{"key": fmt.Sprintf("value-%d", randomNumber)}
dataStr, err := json.Marshal(dataMap)
if err != nil {
return statuses, fmt.Errorf("error marshaling data: %w", err)
}

var body client.BlackListBody

if operation == "create" || operation == "update" {
body = client.BlackListBody{
Type: operation,
ID: fmt.Sprintf("unique-id-%d", randomNumber),
Data: string(dataStr),
Address: fmt.Sprintf("address-%d", randomNumber),
Network: fmt.Sprintf("network-%d", randomNumber),
Tag: fmt.Sprintf("tag-%d", randomNumber),
Remark: "remark",
}
} else {
body = client.BlackListBody{
Type: operation,
ID: fmt.Sprintf("unique-id-%d", randomNumber),
}
}
status, err := apiClient.BlacklistAddress(context.Background(), cfg.AppSecret, cfg.AppID, body)
statuses = append(statuses, status)
if err != nil {
return statuses, fmt.Errorf("error blacklisting address: %w", err)
}
}
return statuses, nil
}

type mockClient struct {
responseMap map[string][]trmlabs.ScreenResponse
}
Expand Down Expand Up @@ -226,3 +263,14 @@ func TestSplitCSV(t *testing.T) {
Equal(t, "false", out["RFQ"][1].Enabled)
Equal(t, "true", out["RFQ"][2].Enabled)
}

func all(t *testing.T, statuses []string, f func(string) bool) {
t.Helper()
for _, status := range statuses {
if !f(status) {
t.Fail()
}
}
}

const success = "success"
124 changes: 124 additions & 0 deletions core/ginhelper/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Gin Helper

Note: gindump utilities are courtesy of [this repo](https://github.com/tpkeeper/gin-dump) and have only been updated/modified here because of dependency issues.
<!--TODO: document non gindump deps-->

* Gin middleware/handler to dump header/body of request and response .

* Very helpful for debugging your applications.

* More beautiful output than httputil.DumpXXX()

## Content-type support / todo

* [x] application/json
* [x] application/x-www-form-urlencoded
* [ ] text/xml
* [ ] application/xml
* [ ] text/plain

## Usage

All:

```go
func main() {
router := gin.Default()

//use Dump() default will print on stdout
router.Use(gindump.Dump())

//or use DumpWithOptions() with more options
router.Use(gindump.DumpWithOptions(true, true, false, true, false, func(dumpStr string) {
fmt.Println(dumpStr)
}))

router.Post("/",myHandler)

...

router.Run()
}
```

Group:

```go
func main() {
router := gin.Default()

dumpGroup := router.Group("/group")

//use Dump() default will print on stdout
dumpGroup.Use(gindump.Dump())

//or use DumpWithOptions() with more options
dumpGroup.Use(gindump.DumpWithOptions(true, true, false, true, false, func(dumpStr string) {
fmt.Println(dumpStr)
}))

dumpGroup.Post("/",myHandler)

...

router.Run()
}

```

EndPoint:

```go
func main() {
router := gin.Default()

//use Dump() default will print on stdout
router.Post("/",gindump.Dump(),myHandler)

//or use DumpWithOptions() with more options
router.Post("/",gindump.DumpWithOptions(true, true, false, true, false, func(dumpStr string) {
fmt.Println(dumpStr)
}),myHandler)

...

router.Run()
}
```


### Output is as follows

```sh
[GIN-dump]:
Request-Header:
{
"Content-Type": [
"application/x-www-form-urlencoded"
]
}
Request-Body:
{
"bar": [
"baz"
],
"foo": [
"bar",
"bar2"
]
}
Response-Header:
{
"Content-Type": [
"application/json; charset=utf-8"
]
}
Response-Body:
{
"data": {
"addr": "[email protected]",
"name": "jfise"
},
"ok": true
}
```
Loading

0 comments on commit 9d19326

Please sign in to comment.