all repos

onasty @ 36f59cd12d0992b16f20057c23cfe0e3c338e637

a one-time notes service
6 files changed, 51 insertions(+), 18 deletions(-)
fix: use separate ratelimiter for some endpoints (#166)

* feat: add slow rate limiter for the specific routes

* chore: update example .env

* fix(e2e): fix tests setup

* fixup! chore: update example .env
Author: Olexandr Smirnov ss2316544@gmail.com
Committed by: GitHub noreply@github.com
Committed at: 2025-07-15 14:03:33 +0300
Parent: d309c75
M .env.example

@@ -50,3 +50,7 @@

RATELIMITER_RPS=100 RATELIMITER_BURST=10 RATELIMITER_TTL=3m + +SLOW_RATELIMITER_RPS=2 +SLOW_RATELIMITER_BURST=1 +SLOW_RATELIMITER_TTL=1m
M cmd/api/main.go

@@ -124,6 +124,12 @@ TTL: cfg.RateLimiterTTL,

Burst: cfg.RateLimiterBurst, } + slowRateLimiterConfig := ratelimit.Config{ + RPS: cfg.SlowRateLimiterRPS, + TTL: cfg.SlowRateLimiterTTL, + Burst: cfg.SlowRateLimiterBurst, + } + handler := httptransport.NewTransport( usersrv, notesrv,

@@ -132,6 +138,7 @@ cfg.AppURL,

cfg.CORSAllowedOrigins, cfg.CORSMaxAge, rateLimiterConfig, + slowRateLimiterConfig, ) // http server
M e2e/e2e_test.go

@@ -143,6 +143,7 @@ cfg.AppURL,

cfg.CORSAllowedOrigins, cfg.CORSMaxAge, ratelimitCfg, + ratelimitCfg, ) e.router = handler.Handler() }
M internal/config/config.go

@@ -60,9 +60,12 @@ LogLevel string

LogFormat string LogShowLine bool - RateLimiterRPS int - RateLimiterBurst int - RateLimiterTTL time.Duration + RateLimiterRPS int + RateLimiterBurst int + RateLimiterTTL time.Duration + SlowRateLimiterRPS int + SlowRateLimiterBurst int + SlowRateLimiterTTL time.Duration } func NewConfig() *Config {

@@ -116,9 +119,12 @@ LogLevel: getenvOrDefault("LOG_LEVEL", "debug"),

LogFormat: getenvOrDefault("LOG_FORMAT", "json"), LogShowLine: getenvOrDefault("LOG_SHOW_LINE", "true") == "true", - RateLimiterRPS: mustGetenvOrDefaultInt("RATELIMITER_RPS", 100), - RateLimiterBurst: mustGetenvOrDefaultInt("RATELIMITER_BURST", 10), - RateLimiterTTL: mustParseDuration(getenvOrDefault("RATELIMITER_TTL", "1m")), + RateLimiterRPS: mustGetenvOrDefaultInt("RATELIMITER_RPS", 100), + RateLimiterBurst: mustGetenvOrDefaultInt("RATELIMITER_BURST", 10), + RateLimiterTTL: mustParseDuration(getenvOrDefault("RATELIMITER_TTL", "1m")), + SlowRateLimiterRPS: mustGetenvOrDefaultInt("SLOW_RATELIMITER_RPS", 2), + SlowRateLimiterBurst: mustGetenvOrDefaultInt("SLOW_RATELIMITER_BURST", 2), + SlowRateLimiterTTL: mustParseDuration(getenvOrDefault("SLOW_RATELIMITER_TTL", "1m")), } }
M internal/transport/http/apiv1/apiv1.go

@@ -5,26 +5,30 @@ "github.com/gin-gonic/gin"

"github.com/olexsmir/onasty/internal/config" "github.com/olexsmir/onasty/internal/service/notesrv" "github.com/olexsmir/onasty/internal/service/usersrv" + "github.com/olexsmir/onasty/internal/transport/http/ratelimit" ) type APIV1 struct { - usersrv usersrv.UserServicer - notesrv notesrv.NoteServicer - env config.Environment - domain string + usersrv usersrv.UserServicer + notesrv notesrv.NoteServicer + slowRatelimitCfg ratelimit.Config + env config.Environment + domain string } func NewAPIV1( us usersrv.UserServicer, ns notesrv.NoteServicer, + slowRatelimitCfg ratelimit.Config, env config.Environment, domain string, ) *APIV1 { return &APIV1{ - usersrv: us, - notesrv: ns, - env: env, - domain: domain, + usersrv: us, + notesrv: ns, + slowRatelimitCfg: slowRatelimitCfg, + env: env, + domain: domain, } }

@@ -36,8 +40,8 @@ auth.POST("/signup", a.signUpHandler)

auth.POST("/signin", a.signInHandler) auth.POST("/refresh-tokens", a.refreshTokensHandler) auth.GET("/verify/:token", a.verifyHandler) - auth.POST("/resend-verification-email", a.resendVerificationEmailHandler) - auth.POST("/reset-password", a.requestResetPasswordHandler) + auth.POST("/resend-verification-email", a.slowRateLimit(), a.resendVerificationEmailHandler) + auth.POST("/reset-password", a.slowRateLimit(), a.requestResetPasswordHandler) auth.POST("/reset-password/:token", a.resetPasswordHandler) oauth := r.Group("/oauth")

@@ -75,3 +79,7 @@ authorized.DELETE(":slug", a.deleteNoteHandler)

} } } + +func (a *APIV1) slowRateLimit() gin.HandlerFunc { + return ratelimit.MiddlewareWithConfig(a.slowRatelimitCfg) +}
M internal/transport/http/http.go

@@ -23,6 +23,7 @@

corsAllowedOrigins []string corsMaxAge time.Duration ratelimitCfg ratelimit.Config + slowRatelimitCfg ratelimit.Config } func NewTransport(

@@ -33,6 +34,7 @@ domain string,

corsAllowedOrigins []string, corsMaxAge time.Duration, ratelimitCfg ratelimit.Config, + slowRatelimitCfg ratelimit.Config, ) *Transport { return &Transport{ usersrv: us,

@@ -42,6 +44,7 @@ domain: domain,

corsAllowedOrigins: corsAllowedOrigins, corsMaxAge: corsMaxAge, ratelimitCfg: ratelimitCfg, + slowRatelimitCfg: slowRatelimitCfg, } }

@@ -56,8 +59,12 @@ ratelimit.MiddlewareWithConfig(t.ratelimitCfg),

) api := r.Group("/api") - api.GET("/ping", t.pingHandler) - apiv1.NewAPIV1(t.usersrv, t.notesrv, t.env, t.domain).Routes(api.Group("/v1")) + { + api.GET("/ping", t.pingHandler) + apiv1. + NewAPIV1(t.usersrv, t.notesrv, t.slowRatelimitCfg, t.env, t.domain). + Routes(api.Group("/v1")) + } return r.Handler() }