feat: enhance security with IP banning, ownership checks, and SSRF protection
All checks were successful
Build Backend / build (push) Successful in 1m56s
Build Backend / build-docker (push) Successful in 1m15s

Add comprehensive security improvements across the application:

- **IP-based login protection**: Implement IP ban system tracking login failures, auto-banning after threshold exceeded
- **Ownership verification**: Add userID parameter to Delete/Update operations for posts and comments to prevent unauthorized modifications
- **SSRF protection**: Add URL and resolved host validation for image URLs in chat and OpenAI client
- **SQL injection prevention**: Add EscapeLikeWildcard utility to escape special characters in LIKE queries
- **HTTP security**: Configure server timeouts and add security headers middleware
- **Rate limiting**: Refactor to support configurable duration and per-endpoint rate limits for auth routes
- **Error handling**: Standardize error responses using HandleError and proper error types
This commit is contained in:
lafay
2026-04-30 12:26:25 +08:00
parent 67a5660952
commit b2b55ea52d
24 changed files with 517 additions and 80 deletions

View File

@@ -3,6 +3,7 @@
import (
"with_you/internal/dto"
"with_you/internal/model"
"with_you/internal/pkg/utils"
"gorm.io/gorm"
)
@@ -119,7 +120,7 @@ func (r *materialFileRepository) List(params dto.MaterialFileQueryParams) ([]*mo
query = query.Where("status = ?", params.Status)
}
if params.Keyword != "" {
keyword := "%" + params.Keyword + "%"
keyword := "%" + utils.EscapeLikeWildcard(params.Keyword) + "%"
query = query.Where("title LIKE ? OR description LIKE ?", keyword, keyword)
}
@@ -223,7 +224,7 @@ func (r *materialFileRepository) Search(keyword string, page, pageSize int) ([]*
query := r.db.Model(&model.MaterialFile{}).Where("status = ?", model.MaterialStatusActive)
if keyword != "" {
kw := "%" + keyword + "%"
kw := "%" + utils.EscapeLikeWildcard(keyword) + "%"
query = query.Where("title LIKE ? OR description LIKE ?", kw, kw)
}

View File

@@ -7,6 +7,7 @@ import (
"with_you/internal/model"
"with_you/internal/pkg/cursor"
"with_you/internal/pkg/utils"
"gorm.io/gorm"
)
@@ -510,10 +511,10 @@ func (r *postRepository) Search(keyword string, page, pageSize int) ([]*model.Po
// 搜索标题和内容
if keyword != "" {
if r.db.Dialector.Name() == "postgres" {
searchPattern := "%" + keyword + "%"
searchPattern := "%" + utils.EscapeLikeWildcard(keyword) + "%"
query = query.Where("title ILIKE ? OR content ILIKE ?", searchPattern, searchPattern)
} else {
searchPattern := "%" + keyword + "%"
searchPattern := "%" + utils.EscapeLikeWildcard(keyword) + "%"
query = query.Where("title LIKE ? OR content LIKE ?", searchPattern, searchPattern)
}
}
@@ -877,10 +878,10 @@ func (r *postRepository) AdminList(query AdminPostListQuery) ([]*model.Post, int
// 关键词搜索(标题或内容)
if query.Keyword != "" {
if r.db.Dialector.Name() == "postgres" {
searchPattern := "%" + query.Keyword + "%"
searchPattern := "%" + utils.EscapeLikeWildcard(query.Keyword) + "%"
db = db.Where("title ILIKE ? OR content ILIKE ?", searchPattern, searchPattern)
} else {
searchPattern := "%" + query.Keyword + "%"
searchPattern := "%" + utils.EscapeLikeWildcard(query.Keyword) + "%"
db = db.Where("title LIKE ? OR content LIKE ?", searchPattern, searchPattern)
}
}
@@ -1125,10 +1126,10 @@ func (r *postRepository) SearchPostsByCursor(ctx context.Context, keyword string
// 搜索标题和内容
if keyword != "" {
if r.db.Dialector.Name() == "postgres" {
searchPattern := "%" + keyword + "%"
searchPattern := "%" + utils.EscapeLikeWildcard(keyword) + "%"
query = query.Where("title ILIKE ? OR content ILIKE ?", searchPattern, searchPattern)
} else {
searchPattern := "%" + keyword + "%"
searchPattern := "%" + utils.EscapeLikeWildcard(keyword) + "%"
query = query.Where("title LIKE ? OR content LIKE ?", searchPattern, searchPattern)
}
}

View File

@@ -2,6 +2,7 @@
import (
"with_you/internal/model"
"with_you/internal/pkg/utils"
"gorm.io/gorm"
)
@@ -69,9 +70,10 @@ func (r *verificationRepository) List(page, pageSize int, status, keyword string
}
if keyword != "" {
kw := "%" + utils.EscapeLikeWildcard(keyword) + "%"
query = query.Joins("JOIN users ON users.id = verification_records.user_id").
Where("users.username LIKE ? OR users.nickname LIKE ? OR verification_records.real_name LIKE ?",
"%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%")
kw, kw, kw)
}
query.Count(&total)