feat: implement rate limiting for public widget endpoints with Redis support

This commit is contained in:
Abhinav Raut
2025-08-19 01:58:13 +05:30
parent 081a5c615a
commit 1962abdc16
6 changed files with 132 additions and 6 deletions
+6 -6
View File
@@ -227,12 +227,12 @@ func initHandlers(g *fastglue.Fastglue, hub *ws.Hub) {
// Widget APIs.
g.GET("/api/v1/widget/chat/settings/launcher", handleGetChatLauncherSettings)
g.GET("/api/v1/widget/chat/settings", handleGetChatSettings)
g.POST("/api/v1/widget/chat/conversations/init", widgetAuth(handleChatInit))
g.POST("/api/v1/widget/chat/conversations", widgetAuth(handleGetConversations))
g.POST("/api/v1/widget/chat/conversations/{uuid}/update-last-seen", widgetAuth(handleChatUpdateLastSeen))
g.POST("/api/v1/widget/chat/conversations/{uuid}", widgetAuth(handleChatGetConversation))
g.POST("/api/v1/widget/chat/conversations/{uuid}/message", widgetAuth(handleChatSendMessage))
g.POST("/api/v1/widget/media/upload", widgetAuth(handleWidgetMediaUpload))
g.POST("/api/v1/widget/chat/conversations/init", rateLimitWidget(widgetAuth(handleChatInit)))
g.POST("/api/v1/widget/chat/conversations", rateLimitWidget(widgetAuth(handleGetConversations)))
g.POST("/api/v1/widget/chat/conversations/{uuid}/update-last-seen", rateLimitWidget(widgetAuth(handleChatUpdateLastSeen)))
g.POST("/api/v1/widget/chat/conversations/{uuid}", rateLimitWidget(widgetAuth(handleChatGetConversation)))
g.POST("/api/v1/widget/chat/conversations/{uuid}/message", rateLimitWidget(widgetAuth(handleChatSendMessage)))
g.POST("/api/v1/widget/media/upload", rateLimitWidget(widgetAuth(handleWidgetMediaUpload)))
// Frontend pages.
g.GET("/", notAuthPage(serveIndexPage))
+10
View File
@@ -36,6 +36,7 @@ import (
notifier "github.com/abhinavxd/libredesk/internal/notification"
emailnotifier "github.com/abhinavxd/libredesk/internal/notification/providers/email"
"github.com/abhinavxd/libredesk/internal/oidc"
"github.com/abhinavxd/libredesk/internal/ratelimit"
"github.com/abhinavxd/libredesk/internal/report"
"github.com/abhinavxd/libredesk/internal/role"
"github.com/abhinavxd/libredesk/internal/search"
@@ -927,3 +928,12 @@ func getLogLevel(lvl string) logf.Level {
return logf.InfoLevel
}
}
// initRateLimit initializes the rate limiter.
func initRateLimit(redisClient *redis.Client) *ratelimit.Limiter {
var config ratelimit.Config
if err := ko.UnmarshalWithConf("rate_limit", &config, koanf.UnmarshalConf{Tag: "toml"}); err != nil {
log.Fatalf("error unmarshalling rate limit config: %v", err)
}
return ratelimit.New(redisClient, config)
}
+4
View File
@@ -35,6 +35,7 @@ import (
"github.com/abhinavxd/libredesk/internal/inbox"
"github.com/abhinavxd/libredesk/internal/media"
"github.com/abhinavxd/libredesk/internal/oidc"
"github.com/abhinavxd/libredesk/internal/ratelimit"
"github.com/abhinavxd/libredesk/internal/role"
"github.com/abhinavxd/libredesk/internal/setting"
"github.com/abhinavxd/libredesk/internal/tag"
@@ -95,6 +96,7 @@ type App struct {
customAttribute *customAttribute.Manager
report *report.Manager
webhook *webhook.Manager
rateLimit *ratelimit.Limiter
// Global state that stores data on an available app update.
update *AppUpdate
@@ -202,6 +204,7 @@ func main() {
sla = initSLA(db, team, settings, businessHours, notifier, template, user, i18n)
conversation = initConversations(i18n, sla, status, priority, wsHub, notifier, db, inbox, user, team, media, settings, csat, automation, template, webhook)
autoassigner = initAutoAssigner(team, user, conversation)
rateLimiter = initRateLimit(rdb)
)
wsHub.SetConversationStore(conversation)
@@ -253,6 +256,7 @@ func main() {
macro: initMacro(db, i18n),
ai: initAI(db, i18n),
webhook: webhook,
rateLimit: rateLimiter,
}
app.consts.Store(constants)
+11
View File
@@ -154,3 +154,14 @@ func getWidgetClaimsOptional(r *fastglue.Request) *Claims {
}
return nil
}
// rateLimitWidget applies rate limiting to widget endpoints.
func rateLimitWidget(handler fastglue.FastRequestHandler) fastglue.FastRequestHandler {
return func(r *fastglue.Request) error {
app := r.Context.(*App)
if err := app.rateLimit.CheckWidgetLimit(r.RequestCtx); err != nil {
return err
}
return handler(r)
}
}