diff --git a/conf/configuration.go b/conf/configuration.go index 8b81af71c..879a01367 100644 --- a/conf/configuration.go +++ b/conf/configuration.go @@ -34,6 +34,8 @@ type configOptions struct { CoverJpegQuality int UIWelcomeMessage string GATrackingID string + AuthRequestLimit int + AuthWindowLength time.Duration // DevFlags. These are used to enable/disable debugging and incomplete features DevLogSourceLine bool @@ -83,7 +85,7 @@ func init() { viper.SetDefault("transcodingcachesize", "100MB") viper.SetDefault("imagecachesize", "100MB") - // Config options only valid for file configuration + // Config options only valid for file/env configuration viper.SetDefault("ignoredarticles", "The El La Los Las Le Les Os As O A") viper.SetDefault("indexgroups", "A B C D E F G H I J K L M N O P Q R S T U V W X-Z(XYZ) [Unknown]([)") viper.SetDefault("probecommand", "ffmpeg %s -f ffmetadata") @@ -91,6 +93,8 @@ func init() { viper.SetDefault("coverjpegquality", 75) viper.SetDefault("uiwelcomemessage", "") viper.SetDefault("gatrackingid", "") + viper.SetDefault("authrequestlimit", 5) + viper.SetDefault("authwindowlength", 20*time.Second) // DevFlags. These are used to enable/disable debugging and incomplete features viper.SetDefault("devlogsourceline", false) diff --git a/go.mod b/go.mod index 834c2ada3..ad2fcd74e 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/dustin/go-humanize v1.0.0 github.com/go-chi/chi v4.1.2+incompatible github.com/go-chi/cors v1.1.1 + github.com/go-chi/httprate v0.4.0 github.com/go-chi/jwtauth v4.0.4+incompatible github.com/google/uuid v1.1.1 github.com/google/wire v0.4.0 diff --git a/go.sum b/go.sum index 374adb848..55b4555f4 100644 --- a/go.sum +++ b/go.sum @@ -45,6 +45,7 @@ github.com/bradleyjkemp/cupaloy v2.3.0+incompatible h1:UafIjBvWQmS9i/xRg+CamMrnL github.com/bradleyjkemp/cupaloy v2.3.0+incompatible/go.mod h1:Au1Xw1sgaJ5iSFktEhYsS0dbQiS1B0/XMXl+42y9Ilk= github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -97,6 +98,8 @@ github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyN github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw= github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= +github.com/go-chi/httprate v0.4.0 h1:M2qVV0w6ksgLs6L8lTrvqNeaVm0ZJNVdbYM8u2T8HaE= +github.com/go-chi/httprate v0.4.0/go.mod h1:7e7qjQtHzEbdyW5TYQrl4X2uNRCnlTajictc7B4ftgc= github.com/go-chi/jwtauth v4.0.4+incompatible h1:LGIxg6YfvSBzxU2BljXbrzVc1fMlgqSKBQgKOGAVtPY= github.com/go-chi/jwtauth v4.0.4+incompatible/go.mod h1:Q5EIArY/QnD6BdS+IyDw7B2m6iNbnPxtfd6/BcmtWbs= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= diff --git a/server/app/app.go b/server/app/app.go index 0ad50da87..d215262ea 100644 --- a/server/app/app.go +++ b/server/app/app.go @@ -9,9 +9,11 @@ import ( "github.com/deluan/navidrome/assets" "github.com/deluan/navidrome/conf" "github.com/deluan/navidrome/core/auth" + "github.com/deluan/navidrome/log" "github.com/deluan/navidrome/model" "github.com/deluan/rest" "github.com/go-chi/chi" + "github.com/go-chi/httprate" "github.com/go-chi/jwtauth" ) @@ -35,7 +37,18 @@ func (app *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (app *Router) routes(path string) http.Handler { r := chi.NewRouter() - r.Post("/login", Login(app.ds)) + if conf.Server.AuthRequestLimit > 0 { + log.Info(context.TODO(), "Login rate limit set", "requestLimit", conf.Server.AuthRequestLimit, + "windowLength", conf.Server.AuthWindowLength) + rateLimiter := httprate.LimitByIP(conf.Server.AuthRequestLimit, conf.Server.AuthWindowLength) + + r.With(rateLimiter).Post("/login", Login(app.ds)) + } else { + log.Warn(context.TODO(), "Login rate limit is disabled! Consider enabling it to be protected against brute-force attacks") + + r.Post("/login", Login(app.ds)) + } + r.Post("/createAdmin", CreateAdmin(app.ds)) r.Route("/api", func(r chi.Router) {