diff --git a/README.md b/README.md index 3a70282..b9a10da 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ import ( "jochum.dev/jo-micro/router" "github.com/urfave/cli/v2" "go-micro.dev/v4" - "wz2100.net/microlobby/shared/proto/authservicepb/v1" + "jochum.dev/jo-micro/auth2/internal/proto/authpb" ) func main() { @@ -95,52 +95,59 @@ func main() { micro.Action(func(c *cli.Context) error { s := service.Server() r := router.NewHandler( - config.RouterURI, - router.NewRoute( - router.Method(router.MethodGet), - router.Path("/"), - router.Endpoint(authservicepb.AuthV1Service.UserList), - router.Params("limit", "offset"), - router.AuthRequired(), - ), - router.NewRoute( - router.Method(router.MethodPost), - router.Path("/login"), - router.Endpoint(authservicepb.AuthV1Service.Login), - ), - router.NewRoute( - router.Method(router.MethodPost), - router.Path("/register"), - router.Endpoint(authservicepb.AuthV1Service.Register), - ), - router.NewRoute( - router.Method(router.MethodPost), - router.Path("/refresh"), - router.Endpoint(authservicepb.AuthV1Service.Refresh), - ), - router.NewRoute( - router.Method(router.MethodDelete), - router.Path("/:userId"), - router.Endpoint(authservicepb.AuthV1Service.UserDelete), - router.Params("userId"), - router.AuthRequired(), - ), - router.NewRoute( - router.Method(router.MethodGet), - router.Path("/:userId"), - router.Endpoint(authservicepb.AuthV1Service.UserDetail), - router.Params("userId"), - router.AuthRequired(), - ), - router.NewRoute( - router.Method(router.MethodPut), - router.Path("/:userId/roles"), - router.Endpoint(authservicepb.AuthV1Service.UserUpdateRoles), - router.Params("userId"), - router.AuthRequired(), - ), + "api/auth/v1", + router.NewRoute( + router.Method(router.MethodGet), + router.Path("/"), + router.Endpoint(authpb.AuthService.List), + router.Params("limit", "offset"), + router.AuthRequired(), + router.RatelimitClientIP("1-M"), + ), + router.NewRoute( + router.Method(router.MethodPost), + router.Path("/login"), + router.Endpoint(authpb.AuthService.Login), + router.RatelimitClientIP("10-M", "30-H", "100-D"), + ), + router.NewRoute( + router.Method(router.MethodPost), + router.Path("/register"), + router.Endpoint(authpb.AuthService.Register), + router.RatelimitClientIP("1-M", "10-H", "50-D"), + ), + router.NewRoute( + router.Method(router.MethodPost), + router.Path("/refresh"), + router.Endpoint(authpb.AuthService.Refresh), + router.RatelimitClientIP("1-M", "10-H", "50-D"), + ), + router.NewRoute( + router.Method(router.MethodDelete), + router.Path("/:userId"), + router.Endpoint(authpb.AuthService.Delete), + router.Params("userId"), + router.AuthRequired(), + router.RatelimitClientIP("10-M"), + ), + router.NewRoute( + router.Method(router.MethodGet), + router.Path("/:userId"), + router.Endpoint(authpb.AuthService.Detail), + router.Params("userId"), + router.AuthRequired(), + router.RatelimitClientIP("100-M"), + ), + router.NewRoute( + router.Method(router.MethodPut), + router.Path("/:userId/roles"), + router.Endpoint(authpb.AuthService.UpdateRoles), + router.Params("userId"), + router.AuthRequired(), + router.RatelimitClientIP("1-M"), + ), ) - r.RegisterWithServer(s) + r.RegisterWithServer(srv.Server()) } ) } diff --git a/cmd/microrouterd/handler/handler.go b/cmd/microrouterd/handler/handler.go index 87d5ccd..6b32f3b 100644 --- a/cmd/microrouterd/handler/handler.go +++ b/cmd/microrouterd/handler/handler.go @@ -7,8 +7,14 @@ import ( "fmt" "io" "net/http" + "strconv" "time" + libredis "github.com/go-redis/redis/v8" + + limiter "github.com/ulule/limiter/v3" + sredis "github.com/ulule/limiter/v3/drivers/store/redis" + "github.com/gin-gonic/gin" "go-micro.dev/v4" "go-micro.dev/v4/client" @@ -32,6 +38,7 @@ type Handler struct { engine *gin.Engine routerAuth auth2.RouterPlugin routes map[string]*routerclientpb.RoutesReply_Route + rlStore limiter.Store } func NewHandler() (*Handler, error) { @@ -40,12 +47,31 @@ func NewHandler() (*Handler, error) { }, nil } -func (h *Handler) Init(service micro.Service, engine *gin.Engine, routerAuth auth2.RouterPlugin, refreshSeconds int) error { +func (h *Handler) Init(service micro.Service, engine *gin.Engine, routerAuth auth2.RouterPlugin, refreshSeconds int, rlStoreURL string) error { h.service = service h.engine = engine h.routerAuth = routerAuth globalGroup := h.engine.Group("") + if rlStoreURL != "" { + // Create a redis client. + option, err := libredis.ParseURL(rlStoreURL) + if err != nil { + return err + } + client := libredis.NewClient(option) + + // Create a store with the redis client. + store, err := sredis.NewStoreWithOptions(client, limiter.StoreOptions{ + Prefix: "rl", + MaxRetry: 10, + }) + if err != nil { + return err + } + h.rlStore = store + } + // Refresh routes for the proxy every 10 seconds go func() { for { @@ -84,17 +110,54 @@ func (h *Handler) Init(service micro.Service, engine *gin.Engine, routerAuth aut } // Calculate the pathMethod of the route and register it if it's not registered yet - pathMethod := fmt.Sprintf("%s:%s%s", route.GetMethod(), g.BasePath(), route.GetPath()) - path := fmt.Sprintf("%s%s", g.BasePath(), route.GetPath()) + pathMethod := fmt.Sprintf("%s:%s%s", route.Method, g.BasePath(), route.Path) + path := fmt.Sprintf("%s%s", g.BasePath(), route.Path) if _, ok := h.routes[pathMethod]; !ok { ilogger.Logrus(). WithField("service", s.Name). - WithField("endpoint", route.GetEndpoint()). - WithField("method", route.GetMethod()). + WithField("endpoint", route.Endpoint). + WithField("method", route.Method). WithField("path", path). - Debugf("Found route") + WithField("ratelimitClientIP", route.RatelimitClientIP). + Debug("found route") + + clientIPRatelimiter := make([]*limiter.Limiter, len(route.RatelimitClientIP)) + if len(route.RatelimitClientIP) > 0 { + if h.rlStore == nil { + ilogger.Logrus(). + WithField("service", s.Name). + WithField("endpoint", route.Endpoint). + WithField("method", route.Method). + WithField("path", path). + WithField("ratelimitClientIP", route.RatelimitClientIP). + Error("found a route with a limiter but there is no limiter store") + continue + } + + haveError := false + for idx, rate := range route.RatelimitClientIP { + rate, err := limiter.NewRateFromFormatted(rate) + if err != nil { + ilogger.Logrus(). + WithField("service", s.Name). + WithField("endpoint", route.Endpoint). + WithField("method", route.Method). + WithField("path", path). + WithField("ratelimitClientIP", route.RatelimitClientIP). + Error(err) + haveError = true + break + } + + clientIPRatelimiter[idx] = limiter.New(h.rlStore, rate) + } + + if haveError { + continue + } + } - g.Handle(route.GetMethod(), route.GetPath(), h.proxy(s.Name, route, route.AuthRequired)) + g.Handle(route.Method, route.Path, h.proxy(s.Name, route, route.AuthRequired, path, clientIPRatelimiter)) h.routes[pathMethod] = route h.routes[pathMethod].Path = path } @@ -112,8 +175,38 @@ func (h *Handler) Stop() error { return nil } -func (h *Handler) proxy(serviceName string, route *routerclientpb.RoutesReply_Route, authRequired bool) func(*gin.Context) { +func (h *Handler) proxy(serviceName string, route *routerclientpb.RoutesReply_Route, authRequired bool, path string, clientIPRatelimiter []*limiter.Limiter) func(*gin.Context) { return func(c *gin.Context) { + + if len(clientIPRatelimiter) > 0 { + for idx, l := range clientIPRatelimiter { + context, err := l.Get(c, fmt.Sprintf("%s-%s-%s", path, l.Rate.Formatted, c.ClientIP())) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "status": http.StatusInternalServerError, + "message": "Internal server error", + }) + c.Abort() + return + } + + if idx == 0 { + c.Header("X-RateLimit-Limit", strconv.FormatInt(context.Limit, 10)) + c.Header("X-RateLimit-Remaining", strconv.FormatInt(context.Remaining, 10)) + c.Header("X-RateLimit-Reset", strconv.FormatInt(context.Reset, 10)) + } + + if context.Reached { + c.JSON(http.StatusTooManyRequests, gin.H{ + "status": http.StatusTooManyRequests, + "message": "To many requests", + }) + c.Abort() + return + } + } + } + // Map query/path params params := make(map[string]string) for _, p := range route.Params { @@ -180,7 +273,7 @@ func (h *Handler) proxy(serviceName string, route *routerclientpb.RoutesReply_Ro request[pn] = p } - req := h.service.Client().NewRequest(serviceName, route.GetEndpoint(), request, client.WithContentType("application/json")) + req := h.service.Client().NewRequest(serviceName, route.Endpoint, request, client.WithContentType("application/json")) // Auth ctx, err := h.routerAuth.ForwardContext(c.Request, c) @@ -217,11 +310,12 @@ func (h *Handler) proxy(serviceName string, route *routerclientpb.RoutesReply_Ro func (h *Handler) Routes(ctx context.Context, in *emptypb.Empty, out *routerserverpb.RoutesReply) error { for _, route := range h.routes { out.Routes = append(out.Routes, &routerserverpb.RoutesReply_Route{ - Method: route.Method, - Path: route.Path, - Params: route.Params, - Endpoint: route.Endpoint, - AuthRequired: route.AuthRequired, + Method: route.Method, + Path: route.Path, + Params: route.Params, + Endpoint: route.Endpoint, + AuthRequired: route.AuthRequired, + RatelimitClientIP: route.RatelimitClientIP, }) } diff --git a/cmd/microrouterd/main.go b/cmd/microrouterd/main.go index 348e4a6..55806e8 100644 --- a/cmd/microrouterd/main.go +++ b/cmd/microrouterd/main.go @@ -54,6 +54,7 @@ func internalService(routerHandler *handler.Handler) { router.Method(router.MethodGet), router.Path("/routes"), router.Endpoint(routerserverpb.RouterServerService.Routes), + router.RatelimitClientIP("1-S", "50-M", "1000-H"), ), ) r.RegisterWithServer(srv.Server()) @@ -113,6 +114,11 @@ func main() { EnvVars: []string{"MICRO_ROUTER_LISTEN"}, Value: ":8080", }, + &cli.StringFlag{ + Name: "router_ratelimiter_store_url", + Usage: "Ratelimiter store URL, for example redis://localhost:6379/0. No store = no Endpoints that require a ratelimiter", + EnvVars: []string{"MICRO_ROUTER_RATELIMITER_STORE_URL"}, + }, }))) routerHandler, err := handler.NewHandler() @@ -144,9 +150,10 @@ func main() { gin.SetMode(gin.ReleaseMode) } r := gin.New() + r.ForwardedByClientIP = true // Initalize the Handler - if err := routerHandler.Init(srv, r, routerAuthReg.Plugin(), c.Int("router_refresh")); err != nil { + if err := routerHandler.Init(srv, r, routerAuthReg.Plugin(), c.Int("router_refresh"), c.String("router_ratelimiter_store_url")); err != nil { ilogger.Logrus().Fatal(err) } diff --git a/go.mod b/go.mod index ff6209d..dab3328 100644 --- a/go.mod +++ b/go.mod @@ -10,8 +10,10 @@ require ( github.com/go-micro/plugins/v4/server/http v1.1.0 github.com/go-micro/plugins/v4/transport/grpc v1.1.0 github.com/go-micro/plugins/v4/transport/nats v1.1.1-0.20220908125827-e0369dde429b + github.com/go-redis/redis/v8 v8.11.5 github.com/sirupsen/logrus v1.9.0 github.com/toorop/gin-logrus v0.0.0-20210225092905-2c785434f26f + github.com/ulule/limiter/v3 v3.10.0 github.com/urfave/cli/v2 v2.16.3 go-micro.dev/v4 v4.8.1 google.golang.org/protobuf v1.28.1 @@ -24,8 +26,10 @@ require ( github.com/acomagu/bufpipe v1.0.3 // indirect github.com/avast/retry-go v3.0.0+incompatible // indirect github.com/bitly/go-simplejson v0.5.0 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cloudflare/circl v1.2.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect @@ -76,7 +80,6 @@ require ( golang.org/x/sync v0.0.0-20220907140024-f12130a52804 // indirect golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect golang.org/x/tools v0.1.12 // indirect google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737 // indirect google.golang.org/grpc v1.49.0 // indirect diff --git a/go.sum b/go.sum index e270c79..6c7d501 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,8 @@ github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngE github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bwesterb/go-ristretto v1.2.1/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= github.com/cloudflare/circl v1.2.0 h1:NheeISPSUcYftKlfrLuOo4T62FkmD4t4jviLfFFYaec= github.com/cloudflare/circl v1.2.0/go.mod h1:Ch2UgYr6ti2KTtlejELlROl0YIYj7SLjAC8M+INXlMk= @@ -27,6 +29,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= @@ -77,6 +81,8 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= @@ -149,6 +155,8 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= @@ -189,6 +197,8 @@ github.com/toorop/gin-logrus v0.0.0-20210225092905-2c785434f26f/go.mod h1:X3Dd1S github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/ulule/limiter/v3 v3.10.0 h1:C9mx3tgxYnt4pUYKWktZf7aEOVPbRYxR+onNFjQTEp0= +github.com/ulule/limiter/v3 v3.10.0/go.mod h1:NqPA/r8QfP7O11iC+95X6gcWJPtRWjKrtOUw07BTvoo= github.com/urfave/cli/v2 v2.16.3 h1:gHoFIwpPjoyIMbJp/VFd+/vuD0dAgFK4B6DpEMFJfQk= github.com/urfave/cli/v2 v2.16.3/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= @@ -262,7 +272,6 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ= -golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= @@ -301,7 +310,5 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -jochum.dev/jo-micro/auth2 v0.2.0 h1:48fLUtVx2u5NDlO0Nzm8K8cHUPbOXNNuKG9gg4vVmFM= -jochum.dev/jo-micro/auth2 v0.2.0/go.mod h1:z2qub/2K5+C9bpoDImWWPpKFPxds6LQN1Qx6X8c1xEc= jochum.dev/jo-micro/auth2 v0.3.0 h1:7r9Vo7/Wlc/tB8sBN4BGQFniOy2x1ekoWI5I4+xBUv0= jochum.dev/jo-micro/auth2 v0.3.0/go.mod h1:fSWMvxDct/jhmP1CC5HQTSfklqZRmygO8Zv5qZVuUHg= diff --git a/handler.go b/handler.go index 421219a..ca4333d 100644 --- a/handler.go +++ b/handler.go @@ -25,12 +25,13 @@ func NewHandler(routerURI string, routes ...*Route) *Handler { } pbRoutes = append(pbRoutes, &routerclientpb.RoutesReply_Route{ - IsGlobal: r.IsGlobal, - Method: r.Method, - Path: r.Path, - Endpoint: util.ReflectFunctionName(r.Endpoint), - Params: r.Params, - AuthRequired: r.AuthRequired, + IsGlobal: r.IsGlobal, + Method: r.Method, + Path: r.Path, + Endpoint: util.ReflectFunctionName(r.Endpoint), + Params: r.Params, + AuthRequired: r.AuthRequired, + RatelimitClientIP: r.RatelimitClientIP, }) } diff --git a/internal/proto/routerclientpb/routerclientpb.pb.go b/internal/proto/routerclientpb/routerclientpb.pb.go index ead84ff..29813ea 100644 --- a/internal/proto/routerclientpb/routerclientpb.pb.go +++ b/internal/proto/routerclientpb/routerclientpb.pb.go @@ -82,12 +82,13 @@ type RoutesReply_Route struct { unknownFields protoimpl.UnknownFields // isGlobal=True == no prefix route - IsGlobal bool `protobuf:"varint,1,opt,name=isGlobal,proto3" json:"isGlobal,omitempty"` - Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"` - Path string `protobuf:"bytes,3,opt,name=path,proto3" json:"path,omitempty"` - Endpoint string `protobuf:"bytes,4,opt,name=endpoint,proto3" json:"endpoint,omitempty"` - Params []string `protobuf:"bytes,5,rep,name=params,proto3" json:"params,omitempty"` - AuthRequired bool `protobuf:"varint,6,opt,name=authRequired,proto3" json:"authRequired,omitempty"` + IsGlobal bool `protobuf:"varint,1,opt,name=isGlobal,proto3" json:"isGlobal,omitempty"` + Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"` + Path string `protobuf:"bytes,3,opt,name=path,proto3" json:"path,omitempty"` + Endpoint string `protobuf:"bytes,4,opt,name=endpoint,proto3" json:"endpoint,omitempty"` + Params []string `protobuf:"bytes,5,rep,name=params,proto3" json:"params,omitempty"` + AuthRequired bool `protobuf:"varint,6,opt,name=authRequired,proto3" json:"authRequired,omitempty"` + RatelimitClientIP []string `protobuf:"bytes,7,rep,name=ratelimitClientIP,proto3" json:"ratelimitClientIP,omitempty"` } func (x *RoutesReply_Route) Reset() { @@ -164,6 +165,13 @@ func (x *RoutesReply_Route) GetAuthRequired() bool { return false } +func (x *RoutesReply_Route) GetRatelimitClientIP() []string { + if x != nil { + return x.RatelimitClientIP + } + return nil +} + var File_routerclientpb_proto protoreflect.FileDescriptor var file_routerclientpb_proto_rawDesc = []byte{ @@ -171,13 +179,13 @@ var file_routerclientpb_proto_rawDesc = []byte{ 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x70, 0x62, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x22, 0x90, 0x02, 0x0a, 0x0b, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, + 0x6f, 0x74, 0x6f, 0x22, 0xbe, 0x02, 0x0a, 0x0b, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x55, 0x52, 0x49, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x55, 0x52, 0x49, 0x12, 0x39, 0x0a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x70, 0x62, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x2e, 0x52, - 0x6f, 0x75, 0x74, 0x65, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x1a, 0xa7, 0x01, 0x0a, + 0x6f, 0x75, 0x74, 0x65, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x1a, 0xd5, 0x01, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x73, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, @@ -188,17 +196,20 @@ var file_routerclientpb_proto_rawDesc = []byte{ 0x72, 0x61, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x52, 0x65, - 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x32, 0x56, 0x0a, 0x13, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, - 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3f, 0x0a, - 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, - 0x1b, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x70, 0x62, - 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x40, - 0x5a, 0x3e, 0x6a, 0x6f, 0x63, 0x68, 0x75, 0x6d, 0x2e, 0x64, 0x65, 0x76, 0x2f, 0x6a, 0x6f, 0x2d, - 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x70, - 0x62, 0x3b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x70, 0x62, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x2c, 0x0a, 0x11, 0x72, 0x61, 0x74, 0x65, 0x6c, 0x69, + 0x6d, 0x69, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x50, 0x18, 0x07, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x11, 0x72, 0x61, 0x74, 0x65, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x43, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x49, 0x50, 0x32, 0x56, 0x0a, 0x13, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3f, 0x0a, 0x06, 0x52, + 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1b, 0x2e, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x70, 0x62, 0x2e, 0x52, + 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x40, 0x5a, 0x3e, + 0x6a, 0x6f, 0x63, 0x68, 0x75, 0x6d, 0x2e, 0x64, 0x65, 0x76, 0x2f, 0x6a, 0x6f, 0x2d, 0x6d, 0x69, + 0x63, 0x72, 0x6f, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x70, 0x62, 0x3b, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x70, 0x62, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/internal/proto/routerclientpb/routerclientpb.proto b/internal/proto/routerclientpb/routerclientpb.proto index 078f126..dc7d524 100644 --- a/internal/proto/routerclientpb/routerclientpb.proto +++ b/internal/proto/routerclientpb/routerclientpb.proto @@ -19,6 +19,7 @@ message RoutesReply { string endpoint = 4; repeated string params = 5; bool authRequired = 6; + repeated string ratelimitClientIP = 7; } string routerURI = 1; diff --git a/internal/proto/routerserverpb/routerserverpb.pb.go b/internal/proto/routerserverpb/routerserverpb.pb.go index 5fc1722..6a1d46d 100644 --- a/internal/proto/routerserverpb/routerserverpb.pb.go +++ b/internal/proto/routerserverpb/routerserverpb.pb.go @@ -73,11 +73,12 @@ type RoutesReply_Route struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Method string `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"` - Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` - Params []string `protobuf:"bytes,3,rep,name=params,proto3" json:"params,omitempty"` - Endpoint string `protobuf:"bytes,4,opt,name=endpoint,proto3" json:"endpoint,omitempty"` - AuthRequired bool `protobuf:"varint,5,opt,name=authRequired,proto3" json:"authRequired,omitempty"` + Method string `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"` + Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` + Params []string `protobuf:"bytes,3,rep,name=params,proto3" json:"params,omitempty"` + Endpoint string `protobuf:"bytes,4,opt,name=endpoint,proto3" json:"endpoint,omitempty"` + AuthRequired bool `protobuf:"varint,5,opt,name=authRequired,proto3" json:"authRequired,omitempty"` + RatelimitClientIP []string `protobuf:"bytes,6,rep,name=ratelimitClientIP,proto3" json:"ratelimitClientIP,omitempty"` } func (x *RoutesReply_Route) Reset() { @@ -147,6 +148,13 @@ func (x *RoutesReply_Route) GetAuthRequired() bool { return false } +func (x *RoutesReply_Route) GetRatelimitClientIP() []string { + if x != nil { + return x.RatelimitClientIP + } + return nil +} + var File_routerserverpb_proto protoreflect.FileDescriptor var file_routerserverpb_proto_rawDesc = []byte{ @@ -154,11 +162,11 @@ var file_routerserverpb_proto_rawDesc = []byte{ 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x70, 0x62, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x22, 0xd6, 0x01, 0x0a, 0x0b, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, + 0x6f, 0x74, 0x6f, 0x22, 0x84, 0x02, 0x0a, 0x0b, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x39, 0x0a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x70, 0x62, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, - 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x1a, 0x8b, + 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x1a, 0xb9, 0x01, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, @@ -167,17 +175,20 @@ var file_routerserverpb_proto_rawDesc = []byte{ 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, - 0x61, 0x75, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x32, 0x56, 0x0a, 0x13, - 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x12, 0x3f, 0x0a, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x16, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1b, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x70, 0x62, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x70, - 0x6c, 0x79, 0x22, 0x00, 0x42, 0x40, 0x5a, 0x3e, 0x6a, 0x6f, 0x63, 0x68, 0x75, 0x6d, 0x2e, 0x64, - 0x65, 0x76, 0x2f, 0x6a, 0x6f, 0x2d, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x2f, 0x72, 0x6f, 0x75, 0x74, - 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x70, 0x62, 0x3b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x75, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x2c, 0x0a, 0x11, + 0x72, 0x61, 0x74, 0x65, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, + 0x50, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x72, 0x61, 0x74, 0x65, 0x6c, 0x69, 0x6d, + 0x69, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x50, 0x32, 0x56, 0x0a, 0x13, 0x52, 0x6f, + 0x75, 0x74, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x3f, 0x0a, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x1a, 0x1b, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x70, 0x62, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, + 0x22, 0x00, 0x42, 0x40, 0x5a, 0x3e, 0x6a, 0x6f, 0x63, 0x68, 0x75, 0x6d, 0x2e, 0x64, 0x65, 0x76, + 0x2f, 0x6a, 0x6f, 0x2d, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x70, 0x62, 0x3b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/internal/proto/routerserverpb/routerserverpb.proto b/internal/proto/routerserverpb/routerserverpb.proto index 5166ac4..f863c6b 100644 --- a/internal/proto/routerserverpb/routerserverpb.proto +++ b/internal/proto/routerserverpb/routerserverpb.proto @@ -17,6 +17,7 @@ message RoutesReply { repeated string params = 3; string endpoint = 4; bool authRequired = 5; + repeated string ratelimitClientIP = 6; } repeated Route routes = 1; diff --git a/route.go b/route.go index 93936a9..93e3e19 100644 --- a/route.go +++ b/route.go @@ -5,25 +5,27 @@ import ( ) type Route struct { - // isGlobal=True == no prefix route - IsGlobal bool + IsGlobal bool // isGlobal=True == no prefix route Method string Path string Endpoint interface{} Params []string - AuthRequired bool + AuthRequired bool // Default false + // https://github.com/ulule/limiter - default is no rate Limiter at all, put the strictes limit first + RatelimitClientIP []string } type Option func(*Route) func NewRoute(opts ...Option) *Route { route := &Route{ - IsGlobal: false, - Method: MethodGet, - Path: "/", - Endpoint: nil, - Params: []string{}, - AuthRequired: false, + IsGlobal: false, + Method: MethodGet, + Path: "/", + Endpoint: nil, + Params: []string{}, + AuthRequired: false, + RatelimitClientIP: []string{}, } for _, o := range opts { @@ -73,3 +75,9 @@ func AuthRequired() Option { o.AuthRequired = true } } + +func RatelimitClientIP(n ...string) Option { + return func(o *Route) { + o.RatelimitClientIP = n + } +}