fix(security): enforce mutation visibility checks
diff --git a/internal/controller/answer_controller.go b/internal/controller/answer_controller.go index 6e16c6a..58e55c1 100644 --- a/internal/controller/answer_controller.go +++ b/internal/controller/answer_controller.go
@@ -208,6 +208,7 @@ }() req.QuestionID = uid.DeShortID(req.QuestionID) req.UserID = middleware.GetLoginUserIDFromContext(ctx) + req.IsAdminModerator = middleware.GetUserIsAdminModerator(ctx) canList, err := ac.rankService.CheckOperationPermissions(ctx, req.UserID, []string{ permission.AnswerEdit, @@ -220,7 +221,7 @@ } linkUrlLimitUser := canList[2] - isAdmin := middleware.GetUserIsAdminModerator(ctx) + isAdmin := req.IsAdminModerator if !isAdmin || !linkUrlLimitUser { captchaPass := ac.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionAnswer, req.UserID, req.CaptchaID, req.CaptchaCode) if !captchaPass {
diff --git a/internal/controller/comment_controller.go b/internal/controller/comment_controller.go index e0f18a8..4bdd8ac 100644 --- a/internal/controller/comment_controller.go +++ b/internal/controller/comment_controller.go
@@ -88,6 +88,7 @@ }() req.ObjectID = uid.DeShortID(req.ObjectID) req.UserID = middleware.GetLoginUserIDFromContext(ctx) + req.IsAdminModerator = middleware.GetUserIsAdminModerator(ctx) canList, err := cc.rankService.CheckOperationPermissions(ctx, req.UserID, []string{ permission.CommentAdd, @@ -100,7 +101,7 @@ return } linkUrlLimitUser := canList[3] - isAdmin := middleware.GetUserIsAdminModerator(ctx) + isAdmin := req.IsAdminModerator if !isAdmin || !linkUrlLimitUser { captchaPass := cc.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionComment, req.UserID, req.CaptchaID, req.CaptchaCode) if !captchaPass {
diff --git a/internal/controller/report_controller.go b/internal/controller/report_controller.go index 13b4c09..53b298e 100644 --- a/internal/controller/report_controller.go +++ b/internal/controller/report_controller.go
@@ -73,7 +73,8 @@ } req.ObjectID = uid.DeShortID(req.ObjectID) req.UserID = middleware.GetLoginUserIDFromContext(ctx) - isAdmin := middleware.GetUserIsAdminModerator(ctx) + req.IsAdminModerator = middleware.GetUserIsAdminModerator(ctx) + isAdmin := req.IsAdminModerator if !isAdmin { captchaPass := rc.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionReport, req.UserID, req.CaptchaID, req.CaptchaCode) if !captchaPass {
diff --git a/internal/controller/vote_controller.go b/internal/controller/vote_controller.go index 3027966..10ce42a 100644 --- a/internal/controller/vote_controller.go +++ b/internal/controller/vote_controller.go
@@ -72,6 +72,7 @@ } req.ObjectID = uid.DeShortID(req.ObjectID) req.UserID = middleware.GetLoginUserIDFromContext(ctx) + req.IsAdminModerator = middleware.GetUserIsAdminModerator(ctx) can, needRank, err := vc.rankService.CheckVotePermission(ctx, req.UserID, req.ObjectID, true) if err != nil { @@ -85,7 +86,7 @@ return } - isAdmin := middleware.GetUserIsAdminModerator(ctx) + isAdmin := req.IsAdminModerator if !isAdmin { captchaPass := vc.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionVote, req.UserID, req.CaptchaID, req.CaptchaCode) if !captchaPass { @@ -126,7 +127,8 @@ } req.ObjectID = uid.DeShortID(req.ObjectID) req.UserID = middleware.GetLoginUserIDFromContext(ctx) - isAdmin := middleware.GetUserIsAdminModerator(ctx) + req.IsAdminModerator = middleware.GetUserIsAdminModerator(ctx) + isAdmin := req.IsAdminModerator can, needRank, err := vc.rankService.CheckVotePermission(ctx, req.UserID, req.ObjectID, false) if err != nil {
diff --git a/internal/schema/answer_schema.go b/internal/schema/answer_schema.go index bf80c56..e81b863 100644 --- a/internal/schema/answer_schema.go +++ b/internal/schema/answer_schema.go
@@ -47,17 +47,18 @@ ) type AnswerAddReq struct { - QuestionID string `json:"question_id"` - Content string `validate:"required,notblank,gte=6,lte=65535" json:"content"` - HTML string `json:"-"` - UserID string `json:"-"` - CanEdit bool `json:"-"` - CanDelete bool `json:"-"` - CanRecover bool `json:"-"` - CaptchaID string `json:"captcha_id"` - CaptchaCode string `json:"captcha_code"` - IP string `json:"-"` - UserAgent string `json:"-"` + QuestionID string `json:"question_id"` + Content string `validate:"required,notblank,gte=6,lte=65535" json:"content"` + HTML string `json:"-"` + UserID string `json:"-"` + IsAdminModerator bool `json:"-"` + CanEdit bool `json:"-"` + CanDelete bool `json:"-"` + CanRecover bool `json:"-"` + CaptchaID string `json:"captcha_id"` + CaptchaCode string `json:"captcha_code"` + IP string `json:"-"` + UserAgent string `json:"-"` } func (req *AnswerAddReq) Check() (errFields []*validator.FormErrorField, err error) {
diff --git a/internal/schema/comment_schema.go b/internal/schema/comment_schema.go index 8297c88..0fe5ee0 100644 --- a/internal/schema/comment_schema.go +++ b/internal/schema/comment_schema.go
@@ -44,7 +44,8 @@ CaptchaCode string `json:"captcha_code"` // user id - UserID string `json:"-"` + UserID string `json:"-"` + IsAdminModerator bool `json:"-"` // whether user can add it CanAdd bool `json:"-"` // whether user can edit it
diff --git a/internal/schema/report_schema.go b/internal/schema/report_schema.go index 1f702df..3c1e332 100644 --- a/internal/schema/report_schema.go +++ b/internal/schema/report_schema.go
@@ -28,9 +28,10 @@ // report content Content string `validate:"omitempty,gt=0,lte=500" json:"content"` // user id - UserID string `json:"-"` - CaptchaID string `json:"captcha_id"` // captcha_id - CaptchaCode string `json:"captcha_code"` + UserID string `json:"-"` + IsAdminModerator bool `json:"-"` + CaptchaID string `json:"captcha_id"` // captcha_id + CaptchaCode string `json:"captcha_code"` } // GetReportListReq get report list all request
diff --git a/internal/schema/vote_schema.go b/internal/schema/vote_schema.go index e82adcc..15a5479 100644 --- a/internal/schema/vote_schema.go +++ b/internal/schema/vote_schema.go
@@ -20,11 +20,12 @@ package schema type VoteReq struct { - ObjectID string `validate:"required" json:"object_id"` - IsCancel bool `validate:"omitempty" json:"is_cancel"` - CaptchaID string `json:"captcha_id"` - CaptchaCode string `json:"captcha_code"` - UserID string `json:"-"` + ObjectID string `validate:"required" json:"object_id"` + IsCancel bool `validate:"omitempty" json:"is_cancel"` + CaptchaID string `json:"captcha_id"` + CaptchaCode string `json:"captcha_code"` + UserID string `json:"-"` + IsAdminModerator bool `json:"-"` } type VoteResp struct {
diff --git a/internal/service/comment/comment_service.go b/internal/service/comment/comment_service.go index 455ab86..0cbf202 100644 --- a/internal/service/comment/comment_service.go +++ b/internal/service/comment/comment_service.go
@@ -144,6 +144,9 @@ if objInfo.IsDeleted() { return nil, errors.BadRequest(reason.NewObjectAlreadyDeleted) } + if err := objInfo.CheckVisibility(req.UserID, req.IsAdminModerator); err != nil { + return nil, err + } objInfo.ObjectID = uid.DeShortID(objInfo.ObjectID) objInfo.QuestionID = uid.DeShortID(objInfo.QuestionID) objInfo.AnswerID = uid.DeShortID(objInfo.AnswerID)
diff --git a/internal/service/content/answer_service.go b/internal/service/content/answer_service.go index bda7b58..e43467a 100644 --- a/internal/service/content/answer_service.go +++ b/internal/service/content/answer_service.go
@@ -258,6 +258,14 @@ if !exist { return "", errors.BadRequest(reason.QuestionNotFound) } + if err := (&schema.SimpleObjectInfo{ + ObjectType: constant.QuestionObjectType, + QuestionCreatorUserID: questionInfo.UserID, + QuestionStatus: questionInfo.Status, + QuestionShow: questionInfo.Show, + }).CheckVisibility(req.UserID, req.IsAdminModerator); err != nil { + return "", err + } if questionInfo.Status == entity.QuestionStatusClosed || questionInfo.Status == entity.QuestionStatusDeleted { err = errors.BadRequest(reason.AnswerCannotAddByClosedQuestion) return "", err
diff --git a/internal/service/content/vote_service.go b/internal/service/content/vote_service.go index 1f74769..0456543 100644 --- a/internal/service/content/vote_service.go +++ b/internal/service/content/vote_service.go
@@ -94,6 +94,9 @@ if objectInfo.IsDeleted() { return nil, errors.BadRequest(reason.NewObjectAlreadyDeleted) } + if err := objectInfo.CheckVisibility(req.UserID, req.IsAdminModerator); err != nil { + return nil, err + } // make object id must be decoded objectInfo.ObjectID = req.ObjectID @@ -145,6 +148,9 @@ if objectInfo.IsDeleted() { return nil, errors.BadRequest(reason.NewObjectAlreadyDeleted) } + if err := objectInfo.CheckVisibility(req.UserID, req.IsAdminModerator); err != nil { + return nil, err + } // make object id must be decoded objectInfo.ObjectID = req.ObjectID
diff --git a/internal/service/report/report_service.go b/internal/service/report/report_service.go index 3edbc1b..c57d83e 100644 --- a/internal/service/report/report_service.go +++ b/internal/service/report/report_service.go
@@ -99,6 +99,9 @@ if objInfo.IsDeleted() { return errors.BadRequest(reason.NewObjectAlreadyDeleted) } + if err := objInfo.CheckVisibility(req.UserID, req.IsAdminModerator); err != nil { + return err + } cf, err := rs.configService.GetConfigByID(ctx, req.ReportType) if err != nil || cf == nil {
diff --git a/internal/service/user_admin/user_backyard.go b/internal/service/user_admin/user_backyard.go index 072feec..acbaf99 100644 --- a/internal/service/user_admin/user_backyard.go +++ b/internal/service/user_admin/user_backyard.go
@@ -170,6 +170,7 @@ if err := us.revokeUserAPIKeys(ctx, userInfo.ID); err != nil { return err } + us.authService.RemoveUserAllTokens(ctx, userInfo.ID) } // remove all content that user created, such as question, answer, comment, etc.