Files
backend/internal/service/sticker_service.go
lafay b2b55ea52d
All checks were successful
Build Backend / build (push) Successful in 1m56s
Build Backend / build-docker (push) Successful in 1m15s
feat: enhance security with IP banning, ownership checks, and SSRF protection
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
2026-04-30 12:26:25 +08:00

141 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"net/url"
"strings"
apperrors "with_you/internal/errors"
"with_you/internal/model"
"with_you/internal/repository"
)
var (
ErrStickerAlreadyExists = apperrors.ErrStickerAlreadyExists
ErrInvalidStickerURL = apperrors.ErrInvalidStickerURL
)
// StickerService 自定义表情服务接口
type StickerService interface {
// 获取用户的所有表情
GetUserStickers(userID string) ([]model.UserSticker, error)
// 添加表情
AddSticker(userID string, url string, width, height int) (*model.UserSticker, error)
// 删除表情
DeleteSticker(userID string, stickerID string) error
// 检查表情是否已存在
CheckExists(userID string, url string) (bool, error)
// 重新排序
ReorderStickers(userID string, orders map[string]int) error
// 获取用户表情数量
GetStickerCount(userID string) (int64, error)
}
// stickerService 自定义表情服务实现
type stickerService struct {
stickerRepo repository.StickerRepository
}
// NewStickerService 创建自定义表情服务
func NewStickerService(stickerRepo repository.StickerRepository) StickerService {
return &stickerService{
stickerRepo: stickerRepo,
}
}
// GetUserStickers 获取用户的所有表情
func (s *stickerService) GetUserStickers(userID string) ([]model.UserSticker, error) {
stickers, err := s.stickerRepo.GetByUserID(userID)
if err != nil {
return nil, err
}
// 兼容历史脏数据:过滤本地文件 URI避免客户端加载 file:// 报错
filtered := make([]model.UserSticker, 0, len(stickers))
for _, sticker := range stickers {
if isValidStickerURL(sticker.URL) {
filtered = append(filtered, sticker)
}
}
return filtered, nil
}
// AddSticker 添加表情
func (s *stickerService) AddSticker(userID string, url string, width, height int) (*model.UserSticker, error) {
if !isValidStickerURL(url) {
return nil, ErrInvalidStickerURL
}
// 检查是否已存在
exists, err := s.stickerRepo.Exists(userID, url)
if err != nil {
return nil, err
}
if exists {
return nil, ErrStickerAlreadyExists
}
// 获取当前数量用于设置排序
count, err := s.stickerRepo.CountByUserID(userID)
if err != nil {
return nil, err
}
sticker := &model.UserSticker{
UserID: userID,
URL: url,
Width: width,
Height: height,
SortOrder: int(count), // 新表情添加到末尾
}
if err := s.stickerRepo.Create(sticker); err != nil {
return nil, err
}
return sticker, nil
}
func isValidStickerURL(raw string) bool {
trimmed := strings.TrimSpace(raw)
if trimmed == "" {
return false
}
parsed, err := url.Parse(trimmed)
if err != nil {
return false
}
scheme := strings.ToLower(parsed.Scheme)
return scheme == "http" || scheme == "https"
}
// DeleteSticker 删除表情
func (s *stickerService) DeleteSticker(userID string, stickerID string) error {
// 先检查表情是否属于该用户
sticker, err := s.stickerRepo.GetByID(stickerID)
if err != nil {
return err
}
if sticker.UserID != userID {
return apperrors.ErrForbidden
}
return s.stickerRepo.Delete(stickerID)
}
// CheckExists 检查表情是否已存在
func (s *stickerService) CheckExists(userID string, url string) (bool, error) {
return s.stickerRepo.Exists(userID, url)
}
// ReorderStickers 重新排序
func (s *stickerService) ReorderStickers(userID string, orders map[string]int) error {
return s.stickerRepo.BatchUpdateSortOrder(userID, orders)
}
// GetStickerCount 获取用户表情数量
func (s *stickerService) GetStickerCount(userID string) (int64, error) {
return s.stickerRepo.CountByUserID(userID)
}