mirror of
https://github.com/wgh136/nysoure.git
synced 2025-09-27 04:17:23 +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")
|
||||
}
|
||||
|
||||
comment, err := service.CreateComment(req, userID, uint(resourceID))
|
||||
comment, err := service.CreateComment(req, userID, uint(resourceID), c.IP())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -3,12 +3,20 @@ package service
|
||||
import (
|
||||
"nysoure/server/dao"
|
||||
"nysoure/server/model"
|
||||
"nysoure/server/utils"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v3/log"
|
||||
)
|
||||
|
||||
const (
|
||||
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 {
|
||||
@@ -16,7 +24,19 @@ type CommentRequest struct {
|
||||
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 {
|
||||
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) {
|
||||
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 {
|
||||
return nil, model.NewRequestError("Too many images, maximum is 9")
|
||||
}
|
||||
|
@@ -11,7 +11,6 @@ import (
|
||||
"nysoure/server/utils"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v3/log"
|
||||
@@ -55,39 +54,11 @@ func init() {
|
||||
}
|
||||
|
||||
var (
|
||||
imageUploadsByIP = make(map[string]uint)
|
||||
imageUploadsLock = sync.RWMutex{}
|
||||
imageLimiter = utils.NewRequestLimiter(maxUploadsPerIP, 24*time.Hour)
|
||||
)
|
||||
|
||||
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) {
|
||||
canUpload, err := checkUserCanUpload(uid)
|
||||
if err != nil {
|
||||
@@ -96,7 +67,7 @@ func CreateImage(uid uint, ip string, data []byte) (uint, error) {
|
||||
}
|
||||
if !canUpload {
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
|
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