1
0
mirror of https://github.com/lus/pasty.git synced 2023-08-10 21:13:09 +03:00

Implement rate limiting

This commit is contained in:
Lukas SP
2020-08-23 23:07:07 +02:00
parent 5272426dac
commit 8317fef5f7
6 changed files with 141 additions and 56 deletions

View File

@ -0,0 +1,56 @@
package v1
import (
"encoding/json"
"github.com/Lukaesebrot/pasty/internal/pastes"
"github.com/Lukaesebrot/pasty/internal/storage"
"github.com/valyala/fasthttp"
)
// HastebinSupportHandler handles the legacy hastebin requests
func HastebinSupportHandler(ctx *fasthttp.RequestCtx) {
// Define the paste content
var content string
switch string(ctx.Request.Header.ContentType()) {
case "text/plain":
content = string(ctx.PostBody())
break
case "multipart/form-data":
content = string(ctx.FormValue("data"))
break
default:
ctx.SetStatusCode(fasthttp.StatusBadRequest)
ctx.SetBodyString("invalid content type")
return
}
// Create the paste object
paste, err := pastes.Create(content)
if err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
ctx.SetBodyString(err.Error())
return
}
// Hash the deletion token
err = paste.HashDeletionToken()
if err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
ctx.SetBodyString(err.Error())
return
}
// Save the paste
err = storage.Current.Save(paste)
if err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
ctx.SetBodyString(err.Error())
return
}
// Respond with the paste key
jsonData, _ := json.Marshal(map[string]string{
"key": paste.ID.String(),
})
ctx.SetBody(jsonData)
}

View File

@ -6,14 +6,15 @@ import (
"github.com/Lukaesebrot/pasty/internal/storage"
"github.com/bwmarrin/snowflake"
"github.com/fasthttp/router"
limitFasthttp "github.com/ulule/limiter/v3/drivers/middleware/fasthttp"
"github.com/valyala/fasthttp"
)
// InitializePastesController initializes the '/v1/pastes/*' controller
func InitializePastesController(group *router.Group) {
group.GET("/{id}", v1GetPaste)
group.POST("", v1PostPaste)
group.DELETE("/{id}", v1DeletePaste)
func InitializePastesController(group *router.Group, rateLimiterMiddleware *limitFasthttp.Middleware) {
group.GET("/{id}", rateLimiterMiddleware.Handle(v1GetPaste))
group.POST("", rateLimiterMiddleware.Handle(v1PostPaste))
group.DELETE("/{id}", rateLimiterMiddleware.Handle(v1DeletePaste))
}
// v1GetPaste handles the 'GET /v1/pastes/{id}' endpoint

View File

@ -3,11 +3,12 @@ package web
import (
"encoding/json"
"github.com/Lukaesebrot/pasty/internal/env"
"github.com/Lukaesebrot/pasty/internal/pastes"
"github.com/Lukaesebrot/pasty/internal/static"
"github.com/Lukaesebrot/pasty/internal/storage"
v1 "github.com/Lukaesebrot/pasty/internal/web/controllers/v1"
routing "github.com/fasthttp/router"
"github.com/ulule/limiter/v3"
limitFasthttp "github.com/ulule/limiter/v3/drivers/middleware/fasthttp"
"github.com/ulule/limiter/v3/drivers/store/memory"
"github.com/valyala/fasthttp"
"path/filepath"
"strings"
@ -35,6 +36,14 @@ func Serve() error {
router.NotFound(ctx)
})
// Set up the rate limiter
rate, err := limiter.NewRateFromFormatted(env.Get("RATE_LIMIT", "30-M"))
if err != nil {
return err
}
rateLimiter := limiter.New(memory.NewStore(), rate)
rateLimiterMiddleware := limitFasthttp.NewMiddleware(rateLimiter)
// Route the API endpoints
apiRoute := router.Group("/api")
{
@ -46,13 +55,13 @@ func Serve() error {
})
ctx.SetBody(jsonData)
})
v1.InitializePastesController(v1Route.Group("/pastes"))
v1.InitializePastesController(v1Route.Group("/pastes"), rateLimiterMiddleware)
}
}
// Route the hastebin documents route if hastebin support is enabled
if env.Get("HASTEBIN_SUPPORT", "false") == "true" {
router.POST("/documents", hastebinSupportHandler)
router.POST("/documents", rateLimiterMiddleware.Handle(v1.HastebinSupportHandler))
}
// Serve the web resources
@ -83,51 +92,3 @@ func frontendHandler() fasthttp.RequestHandler {
}
return fs.NewRequestHandler()
}
// hastebinSupportHandler handles the legacy hastebin requests
func hastebinSupportHandler(ctx *fasthttp.RequestCtx) {
// Define the paste content
var content string
switch string(ctx.Request.Header.ContentType()) {
case "text/plain":
content = string(ctx.PostBody())
break
case "multipart/form-data":
content = string(ctx.FormValue("data"))
break
default:
ctx.SetStatusCode(fasthttp.StatusBadRequest)
ctx.SetBodyString("invalid content type")
return
}
// Create the paste object
paste, err := pastes.Create(content)
if err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
ctx.SetBodyString(err.Error())
return
}
// Hash the deletion token
err = paste.HashDeletionToken()
if err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
ctx.SetBodyString(err.Error())
return
}
// Save the paste
err = storage.Current.Save(paste)
if err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
ctx.SetBodyString(err.Error())
return
}
// Respond with the paste key
jsonData, _ := json.Marshal(map[string]string{
"key": paste.ID.String(),
})
ctx.SetBody(jsonData)
}