feat(verification): enforce user verification requirement for posts, comments, and WebSocket connections
Add verification status checks to protected routes including post creation, updates, deletion, likes, favorites, voting, and WebSocket connections. Also rename RequireVerification middleware to RequireVerified and update error response format with string error codes.
This commit is contained in:
@@ -5,6 +5,7 @@ package main
|
||||
|
||||
import (
|
||||
"carrot_bbs/internal/handler"
|
||||
"carrot_bbs/internal/repository"
|
||||
"carrot_bbs/internal/router"
|
||||
"carrot_bbs/internal/service"
|
||||
appwire "carrot_bbs/internal/wire"
|
||||
@@ -24,6 +25,7 @@ func InitializeApp() (*App, error) {
|
||||
|
||||
// ProvideRouter 提供路由
|
||||
func ProvideRouter(
|
||||
userRepo repository.UserRepository,
|
||||
userHandler *handler.UserHandler,
|
||||
postHandler *handler.PostHandler,
|
||||
commentHandler *handler.CommentHandler,
|
||||
@@ -58,6 +60,7 @@ func ProvideRouter(
|
||||
wsHandler *handler.WSHandler,
|
||||
) *router.Router {
|
||||
return router.New(
|
||||
userRepo,
|
||||
userHandler,
|
||||
postHandler,
|
||||
commentHandler,
|
||||
|
||||
@@ -132,8 +132,8 @@ func InitializeApp() (*App, error) {
|
||||
verificationHandler := handler.NewVerificationHandler(verificationService)
|
||||
adminVerificationService := wire.ProvideAdminVerificationService(verificationRepository, userRepository)
|
||||
adminVerificationHandler := wire.ProvideAdminVerificationHandler(adminVerificationService, userRepository)
|
||||
wsHandler := wire.ProvideWSHandler(hub, chatService, groupService, jwtService, callService)
|
||||
router := ProvideRouter(userHandler, postHandler, commentHandler, messageHandler, notificationHandler, uploadHandler, jwtService, pushHandler, systemMessageHandler, groupHandler, stickerHandler, voteHandler, channelHandler, scheduleHandler, roleHandler, adminUserHandler, adminPostHandler, adminCommentHandler, adminGroupHandler, adminDashboardHandler, adminLogHandler, qrCodeHandler, materialHandler, callHandler, reportHandler, adminReportHandler, verificationHandler, adminVerificationHandler, logService, userActivityService, casbinService, wsHandler)
|
||||
wsHandler := wire.ProvideWSHandler(hub, chatService, groupService, jwtService, callService, userRepository)
|
||||
router := ProvideRouter(userRepository, userHandler, postHandler, commentHandler, messageHandler, notificationHandler, uploadHandler, jwtService, pushHandler, systemMessageHandler, groupHandler, stickerHandler, voteHandler, channelHandler, scheduleHandler, roleHandler, adminUserHandler, adminPostHandler, adminCommentHandler, adminGroupHandler, adminDashboardHandler, adminLogHandler, qrCodeHandler, materialHandler, callHandler, reportHandler, adminReportHandler, verificationHandler, adminVerificationHandler, logService, userActivityService, casbinService, wsHandler)
|
||||
hotRankWorker := wire.ProvideHotRankWorker(config, postRepository, cache)
|
||||
app := NewApp(config, db, router, pushService, hotRankWorker, server, logger)
|
||||
return app, nil
|
||||
@@ -143,6 +143,7 @@ func InitializeApp() (*App, error) {
|
||||
|
||||
// ProvideRouter 提供路由
|
||||
func ProvideRouter(
|
||||
userRepo repository.UserRepository,
|
||||
userHandler *handler.UserHandler,
|
||||
postHandler *handler.PostHandler,
|
||||
commentHandler *handler.CommentHandler,
|
||||
@@ -177,6 +178,7 @@ func ProvideRouter(
|
||||
wsHandler *handler.WSHandler,
|
||||
) *router.Router {
|
||||
return router.New(
|
||||
userRepo,
|
||||
userHandler,
|
||||
postHandler,
|
||||
commentHandler,
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/response"
|
||||
"carrot_bbs/internal/pkg/ws"
|
||||
"carrot_bbs/internal/repository"
|
||||
"carrot_bbs/internal/service"
|
||||
)
|
||||
|
||||
@@ -71,6 +72,7 @@ type WSHandler struct {
|
||||
groupService service.GroupService
|
||||
jwtService *service.JWTService
|
||||
callService service.CallService
|
||||
userRepo repository.UserRepository
|
||||
clientSeq uint64
|
||||
}
|
||||
|
||||
@@ -81,6 +83,7 @@ func NewWSHandler(
|
||||
groupService service.GroupService,
|
||||
jwtService *service.JWTService,
|
||||
callService service.CallService,
|
||||
userRepo repository.UserRepository,
|
||||
) *WSHandler {
|
||||
return &WSHandler{
|
||||
wsHub: wsHub,
|
||||
@@ -88,6 +91,7 @@ func NewWSHandler(
|
||||
groupService: groupService,
|
||||
jwtService: jwtService,
|
||||
callService: callService,
|
||||
userRepo: userRepo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +122,18 @@ func (h *WSHandler) HandleWebSocket(c *gin.Context) {
|
||||
}
|
||||
|
||||
userID := claims.UserID
|
||||
|
||||
// 检查用户认证状态
|
||||
user, err := h.userRepo.GetByID(userID)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "获取用户信息失败")
|
||||
return
|
||||
}
|
||||
if user.VerificationStatus != model.VerificationStatusApproved {
|
||||
response.ErrorWithStringCode(c, http.StatusForbidden, "VERIFICATION_REQUIRED", "请先完成身份认证")
|
||||
return
|
||||
}
|
||||
|
||||
zap.L().Info("WebSocket connection attempt",
|
||||
zap.String("user_id", userID),
|
||||
)
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/response"
|
||||
"carrot_bbs/internal/repository"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func RequireVerification(userRepo repository.UserRepository) gin.HandlerFunc {
|
||||
func RequireVerified(userRepo repository.UserRepository) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
@@ -19,13 +21,13 @@ func RequireVerification(userRepo repository.UserRepository) gin.HandlerFunc {
|
||||
|
||||
user, err := userRepo.GetByID(userID.(string))
|
||||
if err != nil {
|
||||
response.HandleError(c, err, "获取用户信息失败")
|
||||
response.InternalServerError(c, "获取用户信息失败")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if user.VerificationStatus != model.VerificationStatusApproved {
|
||||
response.Forbidden(c, "需要完成身份认证")
|
||||
response.ErrorWithStringCode(c, http.StatusForbidden, "VERIFICATION_REQUIRED", "请先完成身份认证")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -184,9 +184,9 @@ func statusCodeFromAppErrorCode(code string) int {
|
||||
|
||||
// ErrorWithStringCode 带字符串错误码的错误响应
|
||||
func ErrorWithStringCode(c *gin.Context, statusCode int, code string, message string) {
|
||||
c.JSON(statusCode, ResponseSnakeCase{
|
||||
Code: statusCode,
|
||||
Message: message,
|
||||
Data: nil,
|
||||
c.JSON(statusCode, gin.H{
|
||||
"code": statusCode,
|
||||
"message": message,
|
||||
"error_code": code,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,12 +6,14 @@ import (
|
||||
"carrot_bbs/internal/handler"
|
||||
"carrot_bbs/internal/middleware"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/repository"
|
||||
"carrot_bbs/internal/service"
|
||||
)
|
||||
|
||||
// Router 路由配置
|
||||
type Router struct {
|
||||
engine *gin.Engine
|
||||
userRepo repository.UserRepository
|
||||
userHandler *handler.UserHandler
|
||||
postHandler *handler.PostHandler
|
||||
commentHandler *handler.CommentHandler
|
||||
@@ -47,6 +49,7 @@ type Router struct {
|
||||
|
||||
// New 创建路由
|
||||
func New(
|
||||
userRepo repository.UserRepository,
|
||||
userHandler *handler.UserHandler,
|
||||
postHandler *handler.PostHandler,
|
||||
commentHandler *handler.CommentHandler,
|
||||
@@ -87,6 +90,7 @@ func New(
|
||||
|
||||
r := &Router{
|
||||
engine: gin.Default(),
|
||||
userRepo: userRepo,
|
||||
userHandler: userHandler,
|
||||
postHandler: postHandler,
|
||||
commentHandler: commentHandler,
|
||||
@@ -249,9 +253,9 @@ func (r *Router) setupRoutes() {
|
||||
posts.GET("", middleware.OptionalAuth(r.jwtService), r.postHandler.List)
|
||||
posts.GET("/search", middleware.OptionalAuth(r.jwtService), r.postHandler.Search)
|
||||
posts.GET("/:id", middleware.OptionalAuth(r.jwtService), r.postHandler.GetByID)
|
||||
posts.POST("", authMiddleware, r.postHandler.Create)
|
||||
posts.PUT("/:id", authMiddleware, r.postHandler.Update)
|
||||
posts.DELETE("/:id", authMiddleware, r.postHandler.Delete)
|
||||
posts.POST("", authMiddleware, middleware.RequireVerified(r.userRepo), r.postHandler.Create)
|
||||
posts.PUT("/:id", authMiddleware, middleware.RequireVerified(r.userRepo), r.postHandler.Update)
|
||||
posts.DELETE("/:id", authMiddleware, middleware.RequireVerified(r.userRepo), r.postHandler.Delete)
|
||||
|
||||
// 浏览量记录(可选认证,允许游客浏览)
|
||||
posts.POST("/:id/view", middleware.OptionalAuth(r.jwtService), r.postHandler.RecordView)
|
||||
@@ -259,18 +263,18 @@ func (r *Router) setupRoutes() {
|
||||
posts.POST("/:id/share", middleware.OptionalAuth(r.jwtService), r.postHandler.RecordShare)
|
||||
|
||||
// 点赞
|
||||
posts.POST("/:id/like", authMiddleware, r.postHandler.Like)
|
||||
posts.DELETE("/:id/like", authMiddleware, r.postHandler.Unlike)
|
||||
posts.POST("/:id/like", authMiddleware, middleware.RequireVerified(r.userRepo), r.postHandler.Like)
|
||||
posts.DELETE("/:id/like", authMiddleware, middleware.RequireVerified(r.userRepo), r.postHandler.Unlike)
|
||||
|
||||
// 收藏
|
||||
posts.POST("/:id/favorite", authMiddleware, r.postHandler.Favorite)
|
||||
posts.DELETE("/:id/favorite", authMiddleware, r.postHandler.Unfavorite)
|
||||
posts.POST("/:id/favorite", authMiddleware, middleware.RequireVerified(r.userRepo), r.postHandler.Favorite)
|
||||
posts.DELETE("/:id/favorite", authMiddleware, middleware.RequireVerified(r.userRepo), r.postHandler.Unfavorite)
|
||||
|
||||
// 投票相关路由
|
||||
posts.POST("/vote", authMiddleware, r.voteHandler.CreateVotePost) // 创建投票帖子
|
||||
posts.POST("/vote", authMiddleware, middleware.RequireVerified(r.userRepo), r.voteHandler.CreateVotePost) // 创建投票帖子
|
||||
posts.GET("/:id/vote", middleware.OptionalAuth(r.jwtService), r.voteHandler.GetVoteResult) // 获取投票结果
|
||||
posts.POST("/:id/vote", authMiddleware, r.voteHandler.Vote) // 投票
|
||||
posts.DELETE("/:id/vote", authMiddleware, r.voteHandler.Unvote) // 取消投票
|
||||
posts.POST("/:id/vote", authMiddleware, middleware.RequireVerified(r.userRepo), r.voteHandler.Vote) // 投票
|
||||
posts.DELETE("/:id/vote", authMiddleware, middleware.RequireVerified(r.userRepo), r.voteHandler.Unvote) // 取消投票
|
||||
}
|
||||
|
||||
// 频道路由(公开)
|
||||
@@ -322,15 +326,15 @@ func (r *Router) setupRoutes() {
|
||||
comments.GET("/post/:id", middleware.OptionalAuth(r.jwtService), r.commentHandler.GetByPostID)
|
||||
comments.GET("/post/:id/cursor", middleware.OptionalAuth(r.jwtService), r.commentHandler.GetByPostIDByCursor) // 帖子评论游标分页
|
||||
comments.GET("/:id", middleware.OptionalAuth(r.jwtService), r.commentHandler.GetByID)
|
||||
comments.POST("", authMiddleware, r.commentHandler.Create)
|
||||
comments.PUT("/:id", authMiddleware, r.commentHandler.Update)
|
||||
comments.DELETE("/:id", authMiddleware, r.commentHandler.Delete)
|
||||
comments.POST("", authMiddleware, middleware.RequireVerified(r.userRepo), r.commentHandler.Create)
|
||||
comments.PUT("/:id", authMiddleware, middleware.RequireVerified(r.userRepo), r.commentHandler.Update)
|
||||
comments.DELETE("/:id", authMiddleware, middleware.RequireVerified(r.userRepo), r.commentHandler.Delete)
|
||||
comments.GET("/:id/replies", middleware.OptionalAuth(r.jwtService), r.commentHandler.GetReplies)
|
||||
comments.GET("/:id/replies/flat", middleware.OptionalAuth(r.jwtService), r.commentHandler.GetRepliesByRootID) // 扁平化分页获取回复
|
||||
comments.GET("/:id/replies/cursor", middleware.OptionalAuth(r.jwtService), r.commentHandler.GetRepliesByRootIDByCursor) // 回复游标分页
|
||||
// 评论点赞
|
||||
comments.POST("/:id/like", authMiddleware, r.commentHandler.Like)
|
||||
comments.DELETE("/:id/like", authMiddleware, r.commentHandler.Unlike)
|
||||
comments.POST("/:id/like", authMiddleware, middleware.RequireVerified(r.userRepo), r.commentHandler.Like)
|
||||
comments.DELETE("/:id/like", authMiddleware, middleware.RequireVerified(r.userRepo), r.commentHandler.Unlike)
|
||||
}
|
||||
|
||||
// 会话路由
|
||||
@@ -342,11 +346,11 @@ func (r *Router) setupRoutes() {
|
||||
// ================================================================
|
||||
conversations.GET("", r.messageHandler.HandleGetConversationList) // 列表
|
||||
conversations.GET("/cursor", r.messageHandler.GetConversationsByCursor) // 会话列表游标分页
|
||||
conversations.POST("", r.messageHandler.HandleCreateConversation) // 创建
|
||||
conversations.POST("", middleware.RequireVerified(r.userRepo), r.messageHandler.HandleCreateConversation) // 创建
|
||||
conversations.GET("/:id", r.messageHandler.HandleGetConversation) // 详情
|
||||
conversations.GET("/:id/messages", r.messageHandler.HandleGetMessages) // 消息列表
|
||||
conversations.GET("/:id/messages/cursor", r.messageHandler.GetMessagesByCursor) // 消息列表游标分页
|
||||
conversations.POST("/:id/messages", r.messageHandler.HandleSendMessage) // 发送消息
|
||||
conversations.POST("/:id/messages", middleware.RequireVerified(r.userRepo), r.messageHandler.HandleSendMessage) // 发送消息
|
||||
conversations.POST("/:id/read", r.messageHandler.HandleMarkRead) // 标记已读
|
||||
conversations.PUT("/:id/pinned", r.messageHandler.HandleSetConversationPinned) // 置顶设置
|
||||
conversations.GET("/unread/count", r.messageHandler.GetUnreadCount) // 未读数
|
||||
@@ -376,7 +380,7 @@ func (r *Router) setupRoutes() {
|
||||
messages.Use(authMiddleware)
|
||||
{
|
||||
// 撤回/删除消息(统一接口)
|
||||
messages.POST("/delete_msg", r.messageHandler.HandleDeleteMsg)
|
||||
messages.POST("/delete_msg", middleware.RequireVerified(r.userRepo), r.messageHandler.HandleDeleteMsg)
|
||||
}
|
||||
|
||||
// 举报路由
|
||||
|
||||
@@ -85,8 +85,9 @@ func ProvideWSHandler(
|
||||
groupService service.GroupService,
|
||||
jwtService *service.JWTService,
|
||||
callService service.CallService,
|
||||
userRepo repository.UserRepository,
|
||||
) *handler.WSHandler {
|
||||
return handler.NewWSHandler(wsHub, chatService, groupService, jwtService, callService)
|
||||
return handler.NewWSHandler(wsHub, chatService, groupService, jwtService, callService, userRepo)
|
||||
}
|
||||
|
||||
// ProvideAdminVerificationHandler 提供管理端身份认证处理器
|
||||
|
||||
Reference in New Issue
Block a user