From f590b4a89dde771c6af559229dd03461c9ffa8c6 Mon Sep 17 00:00:00 2001 From: AiAe Date: Tue, 23 Jun 2026 20:07:53 +0300 Subject: [PATCH 1/2] Rate limit bypass if request from in-game --- cmd/api/server.go | 35 +++++++++++++++++++++++------------ middleware/authentication.go | 11 +++++++++++ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/cmd/api/server.go b/cmd/api/server.go index 518fbd1..22afbee 100644 --- a/cmd/api/server.go +++ b/cmd/api/server.go @@ -53,21 +53,32 @@ func initializeRateLimiter(engine *gin.Engine) { Limit: 100, }) - engine.Use(ratelimit.RateLimiter(store, &ratelimit.Options{ - ErrorHandler: func(c *gin.Context, info ratelimit.Info) { - isWhitelisted := slices.Contains(config.Instance.Server.RateLimitIpWhitelist, c.ClientIP()) + engine.Use(func(c *gin.Context) { + if !config.Instance.IsProduction || slices.Contains(config.Instance.Server.RateLimitIpWhitelist, c.ClientIP()) { + c.Next() + return + } + + user, err := middleware.AuthenticateInGameRequest(c) + + if err == nil && user != nil { + c.Next() + return + } - if !config.Instance.IsProduction || isWhitelisted { - c.Next() - return - } + info := store.Limit(c.ClientIP(), c) + c.Header("X-Rate-Limit-Limit", fmt.Sprintf("%d", info.Limit)) + c.Header("X-Rate-Limit-Remaining", fmt.Sprintf("%v", info.RemainingHits)) + c.Header("X-Rate-Limit-Reset", fmt.Sprintf("%d", info.ResetTime.Unix())) + if info.RateLimited { c.JSON(http.StatusTooManyRequests, gin.H{"error": "Too many requests"}) - }, - KeyFunc: func(c *gin.Context) string { - return c.ClientIP() - }, - })) + c.Abort() + return + } + + c.Next() + }) } // Initializes all the routes for the server. diff --git a/middleware/authentication.go b/middleware/authentication.go index 30198ef..9ab4205 100644 --- a/middleware/authentication.go +++ b/middleware/authentication.go @@ -94,6 +94,17 @@ func authenticateUser(c *gin.Context) (*db.User, *handlers.APIError) { return user, nil } +// AuthenticateInGameRequest authenticates a request using the in-game auth header. +func AuthenticateInGameRequest(c *gin.Context) (*db.User, error) { + token := c.GetHeader("auth") + + if token == "" { + return nil, nil + } + + return authenticateInGame(c, token) +} + // Authenticates a user by their JWT token. // Header - {Authorization: 'Bearer Token`} func authenticateJWT(header string) (*db.User, error) { From 1ef4312c052f6c5c279fd4badf3bdc55f3d672a9 Mon Sep 17 00:00:00 2001 From: AiAe Date: Tue, 23 Jun 2026 20:54:17 +0300 Subject: [PATCH 2/2] Manual bypass list --- cmd/api/server.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/cmd/api/server.go b/cmd/api/server.go index 22afbee..07bce53 100644 --- a/cmd/api/server.go +++ b/cmd/api/server.go @@ -48,6 +48,10 @@ func initializeServer(port int) { // Initializes the rate limiter for the server func initializeRateLimiter(engine *gin.Engine) { + rateLimitBypassRoutes := map[string]struct{}{ + "/v2/mapset/search": {}, + } + store := ratelimit.InMemoryStore(&ratelimit.InMemoryOptions{ Rate: time.Minute, Limit: 100, @@ -59,11 +63,13 @@ func initializeRateLimiter(engine *gin.Engine) { return } - user, err := middleware.AuthenticateInGameRequest(c) + if _, canBypassRoute := rateLimitBypassRoutes[c.Request.URL.Path]; canBypassRoute { + user, err := middleware.AuthenticateInGameRequest(c) - if err == nil && user != nil { - c.Next() - return + if err == nil && user != nil { + c.Next() + return + } } info := store.Limit(c.ClientIP(), c)