mirror of
https://github.com/wgh136/nysoure.git
synced 2025-09-27 12:17:24 +00:00
Implement comment length and IP rate limiting in comment creation
This commit is contained in:
@@ -38,7 +38,7 @@ func createComment(c fiber.Ctx) error {
|
|||||||
return model.NewRequestError("Content cannot be empty")
|
return model.NewRequestError("Content cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
comment, err := service.CreateComment(req, userID, uint(resourceID))
|
comment, err := service.CreateComment(req, userID, uint(resourceID), c.IP())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -3,12 +3,20 @@ package service
|
|||||||
import (
|
import (
|
||||||
"nysoure/server/dao"
|
"nysoure/server/dao"
|
||||||
"nysoure/server/model"
|
"nysoure/server/model"
|
||||||
|
"nysoure/server/utils"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v3/log"
|
"github.com/gofiber/fiber/v3/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
maxImagePerComment = 9
|
maxImagePerComment = 9
|
||||||
|
maxCommentsPerIP = 512 // Maximum number of comments allowed per IP address per day
|
||||||
|
maxCommentLength = 1024 // Maximum length of a comment
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
commentsLimiter = utils.NewRequestLimiter(maxCommentsPerIP, 24*time.Hour)
|
||||||
)
|
)
|
||||||
|
|
||||||
type CommentRequest struct {
|
type CommentRequest struct {
|
||||||
@@ -16,7 +24,19 @@ type CommentRequest struct {
|
|||||||
Images []uint `json:"images"`
|
Images []uint `json:"images"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateComment(req CommentRequest, userID uint, resourceID uint) (*model.CommentView, error) {
|
func CreateComment(req CommentRequest, userID uint, resourceID uint, ip string) (*model.CommentView, error) {
|
||||||
|
if !commentsLimiter.AllowRequest(ip) {
|
||||||
|
log.Warnf("IP %s has exceeded the comment limit of %d comments per day", ip, maxCommentsPerIP)
|
||||||
|
return nil, model.NewRequestError("Too many comments from this IP address, please try again later")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Content) == 0 {
|
||||||
|
return nil, model.NewRequestError("Content cannot be empty")
|
||||||
|
}
|
||||||
|
if len([]rune(req.Content)) > maxCommentLength {
|
||||||
|
return nil, model.NewRequestError("Comment content exceeds maximum length of 1024 characters")
|
||||||
|
}
|
||||||
|
|
||||||
if len(req.Images) > maxImagePerComment {
|
if len(req.Images) > maxImagePerComment {
|
||||||
return nil, model.NewRequestError("Too many images, maximum is 9")
|
return nil, model.NewRequestError("Too many images, maximum is 9")
|
||||||
}
|
}
|
||||||
@@ -83,6 +103,13 @@ func ListCommentsWithUser(username string, page int) ([]model.CommentWithResourc
|
|||||||
}
|
}
|
||||||
|
|
||||||
func UpdateComment(commentID, userID uint, req CommentRequest) (*model.CommentView, error) {
|
func UpdateComment(commentID, userID uint, req CommentRequest) (*model.CommentView, error) {
|
||||||
|
if len(req.Content) == 0 {
|
||||||
|
return nil, model.NewRequestError("Content cannot be empty")
|
||||||
|
}
|
||||||
|
if len([]rune(req.Content)) > maxCommentLength {
|
||||||
|
return nil, model.NewRequestError("Comment content exceeds maximum length of 1024 characters")
|
||||||
|
}
|
||||||
|
|
||||||
if len(req.Images) > maxImagePerComment {
|
if len(req.Images) > maxImagePerComment {
|
||||||
return nil, model.NewRequestError("Too many images, maximum is 9")
|
return nil, model.NewRequestError("Too many images, maximum is 9")
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,6 @@ import (
|
|||||||
"nysoure/server/utils"
|
"nysoure/server/utils"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v3/log"
|
"github.com/gofiber/fiber/v3/log"
|
||||||
@@ -55,39 +54,11 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
imageUploadsByIP = make(map[string]uint)
|
imageLimiter = utils.NewRequestLimiter(maxUploadsPerIP, 24*time.Hour)
|
||||||
imageUploadsLock = sync.RWMutex{}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const maxUploadsPerIP = 100
|
const maxUploadsPerIP = 100
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Initialize the map with a cleanup function to remove old entries
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
time.Sleep(24 * time.Hour) // Cleanup every 24 hours
|
|
||||||
imageUploadsLock.Lock()
|
|
||||||
imageUploadsByIP = make(map[string]uint) // Clear the map
|
|
||||||
imageUploadsLock.Unlock()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func addIpUploadCount(ip string) bool {
|
|
||||||
imageUploadsLock.Lock()
|
|
||||||
defer imageUploadsLock.Unlock()
|
|
||||||
|
|
||||||
count, exists := imageUploadsByIP[ip]
|
|
||||||
if !exists {
|
|
||||||
count = 0
|
|
||||||
}
|
|
||||||
if count >= maxUploadsPerIP {
|
|
||||||
return false // Exceeded upload limit for this IP
|
|
||||||
}
|
|
||||||
imageUploadsByIP[ip] = count + 1
|
|
||||||
return true // Upload count incremented successfully
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateImage(uid uint, ip string, data []byte) (uint, error) {
|
func CreateImage(uid uint, ip string, data []byte) (uint, error) {
|
||||||
canUpload, err := checkUserCanUpload(uid)
|
canUpload, err := checkUserCanUpload(uid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -96,7 +67,7 @@ func CreateImage(uid uint, ip string, data []byte) (uint, error) {
|
|||||||
}
|
}
|
||||||
if !canUpload {
|
if !canUpload {
|
||||||
// For a normal user, check the IP upload limit
|
// For a normal user, check the IP upload limit
|
||||||
if !addIpUploadCount(ip) {
|
if !imageLimiter.AllowRequest(ip) {
|
||||||
return 0, model.NewUnAuthorizedError("You have reached the maximum upload limit")
|
return 0, model.NewUnAuthorizedError("You have reached the maximum upload limit")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
54
server/utils/request_limit.go
Normal file
54
server/utils/request_limit.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RequestLimiter struct {
|
||||||
|
limit int
|
||||||
|
requestsByIP map[string]int
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRequestLimiter(limit int, duration time.Duration) *RequestLimiter {
|
||||||
|
l := &RequestLimiter{
|
||||||
|
limit: limit,
|
||||||
|
requestsByIP: make(map[string]int),
|
||||||
|
}
|
||||||
|
|
||||||
|
if duration > 0 {
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
time.Sleep(duration)
|
||||||
|
l.resetCounts()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rl *RequestLimiter) AllowRequest(ip string) bool {
|
||||||
|
rl.mu.Lock()
|
||||||
|
defer rl.mu.Unlock()
|
||||||
|
|
||||||
|
count, exists := rl.requestsByIP[ip]
|
||||||
|
if !exists {
|
||||||
|
count = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if count >= rl.limit {
|
||||||
|
return false // Exceeded request limit for this IP
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.requestsByIP[ip] = count + 1
|
||||||
|
return true // Request allowed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rl *RequestLimiter) resetCounts() {
|
||||||
|
rl.mu.Lock()
|
||||||
|
defer rl.mu.Unlock()
|
||||||
|
|
||||||
|
rl.requestsByIP = make(map[string]int) // Reset all counts
|
||||||
|
}
|
Reference in New Issue
Block a user