Skip to content

Commit

Permalink
Merge pull request #48 from blnkfinance/24-implement-rate-limit
Browse files Browse the repository at this point in the history
implement rate limit
  • Loading branch information
jerry-enebeli authored Sep 9, 2024
2 parents 3a50c1c + af3bec3 commit 0353457
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 3 deletions.
2 changes: 2 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ func NewAPI(b *blnk.Blnk) *Api {
r.Use(middleware.SecretKeyAuthMiddleware())
}

r.Use(middleware.RateLimitMiddleware(conf))

r.Use(otelgin.Middleware("BLNK"))

r.GET("/", func(c *gin.Context) {
Expand Down
33 changes: 31 additions & 2 deletions api/middleware/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,41 @@ package middleware
import (
"crypto/subtle"
"net/http"
"time"

"github.com/jerry-enebeli/blnk/config"

"github.com/didip/tollbooth/v7"
"github.com/didip/tollbooth/v7/limiter"
"github.com/gin-gonic/gin"
"github.com/jerry-enebeli/blnk/config"
)

// RateLimitMiddleware creates a middleware for rate limiting using Tollbooth
func RateLimitMiddleware(conf *config.Configuration) gin.HandlerFunc {
if conf.RateLimit.RequestsPerSecond == nil || conf.RateLimit.Burst == nil {
// Rate limiting is disabled
return func(c *gin.Context) {
c.Next()
}
}

rps := *conf.RateLimit.RequestsPerSecond
burst := *conf.RateLimit.Burst
ttl := time.Duration(*conf.RateLimit.CleanupIntervalSec) * time.Second

lmt := tollbooth.NewLimiter(rps, &limiter.ExpirableOptions{
DefaultExpirationTTL: ttl,
})
lmt.SetBurst(burst)
return func(c *gin.Context) {
httpError := tollbooth.LimitByRequest(lmt, c.Writer, c.Request)
if httpError != nil {
c.AbortWithStatusJSON(httpError.StatusCode, gin.H{"error": httpError.Message})
return
}
c.Next()
}
}

func SecretKeyAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
conf, err := config.Fetch()
Expand Down
24 changes: 24 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ type Configuration struct {
AccountNumberGeneration AccountNumberGenerationConfig `json:"account_number_generation"`
Notification Notification `json:"notification"`
OtelGrafanaCloud OtelGrafanaCloud `json:"otel_grafana_cloud"`
RateLimit struct {
RequestsPerSecond *float64 `json:"requests_per_second" envconfig:"BLNK_RATE_LIMIT_RPS"`
Burst *int `json:"burst" envconfig:"BLNK_RATE_LIMIT_BURST"`
CleanupIntervalSec *int `json:"cleanup_interval_sec" envconfig:"BLNK_RATE_LIMIT_CLEANUP_INTERVAL_SEC"`
} `json:"rate_limit"`
}

func loadConfigFromFile(file string) error {
Expand Down Expand Up @@ -184,6 +189,25 @@ func (cnf *Configuration) validateAndAddDefaults() error {
log.Printf("Warning: Port not specified in config. Setting default port: %s", DEFAULT_PORT)
}

// Rate limiting is disabled by default (when both RPS and Burst are nil)
if cnf.RateLimit.RequestsPerSecond != nil && cnf.RateLimit.Burst == nil {
defaultBurst := 2 * int(*cnf.RateLimit.RequestsPerSecond)
cnf.RateLimit.Burst = &defaultBurst
log.Printf("Warning: Rate limit burst not specified. Setting default value: %d", defaultBurst)
}
if cnf.RateLimit.RequestsPerSecond == nil && cnf.RateLimit.Burst != nil {
defaultRPS := float64(*cnf.RateLimit.Burst) / 2
cnf.RateLimit.RequestsPerSecond = &defaultRPS
log.Printf("Warning: Rate limit RPS not specified. Setting default value: %.2f", defaultRPS)
}

// Set default cleanup interval if not specified
if cnf.RateLimit.CleanupIntervalSec == nil {
defaultCleanup := 10800 // 3 hours in seconds
cnf.RateLimit.CleanupIntervalSec = &defaultCleanup
log.Printf("Warning: Rate limit cleanup interval not specified. Setting default value: %d seconds", defaultCleanup)
}

return nil
}

Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ require (
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/go-pkgz/expirable-cache/v3 v3.0.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect
Expand All @@ -55,6 +56,7 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deepmap/oapi-codegen v1.12.3 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/didip/tollbooth/v7 v7.0.2
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ github.com/deepmap/oapi-codegen v1.12.3 h1:+DDYKeIwlKChzHjhVtlISegatFevDDazBhtk/
github.com/deepmap/oapi-codegen v1.12.3/go.mod h1:ao2aFwsl/muMHbez870+KelJ1yusV01RznwAFFrVjDc=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/didip/tollbooth/v7 v7.0.2 h1:WYEfusYI6g64cN0qbZgekDrYfuYBZjUZd5+RlWi69p4=
github.com/didip/tollbooth/v7 v7.0.2/go.mod h1:RtRYfEmFGX70+ike5kSndSvLtQ3+F2EAmTI4Un/VXNc=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
Expand All @@ -68,6 +70,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es=
github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
github.com/go-pkgz/expirable-cache/v3 v3.0.0 h1:u3/gcu3sabLYiTCevoRKv+WzjIn5oo7P8XtiXBeRDLw=
github.com/go-pkgz/expirable-cache/v3 v3.0.0/go.mod h1:2OQiDyEGQalYecLWmXprm3maPXeVb5/6/X7yRPYTzec=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
Expand Down Expand Up @@ -112,6 +116,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hibiken/asynq v0.24.1 h1:+5iIEAyA9K/lcSPvx3qoPtsKJeKI5u9aOIvUmSsazEw=
github.com/hibiken/asynq v0.24.1/go.mod h1:u5qVeSbrnfT+vtG5Mq8ZPzQu/BmCKMHvTGb91uy9Tts=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
Expand Down
2 changes: 1 addition & 1 deletion model/balance.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type Balance struct {
InflightCreditBalance *big.Int `json:"inflight_credit_balance"`
DebitBalance *big.Int `json:"debit_balance"`
InflightDebitBalance *big.Int `json:"inflight_debit_balance"`
CurrencyMultiplier float64 `json:"precision"`
CurrencyMultiplier float64 `json:"currency_multiplier"`
LedgerID string `json:"ledger_id"`
IdentityID string `json:"identity_id"`
BalanceID string `json:"balance_id"`
Expand Down

0 comments on commit 0353457

Please sign in to comment.