diff --git a/cmd/api/server.go b/cmd/api/server.go index 518fbd1..07bce53 100644 --- a/cmd/api/server.go +++ b/cmd/api/server.go @@ -48,26 +48,43 @@ 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, }) - 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 + } - if !config.Instance.IsProduction || isWhitelisted { + if _, canBypassRoute := rateLimitBypassRoutes[c.Request.URL.Path]; canBypassRoute { + user, err := middleware.AuthenticateInGameRequest(c) + + if err == nil && user != nil { 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) {