安全: 修复凭据、遥测与账号数据清理风险

This commit is contained in:
tamakyi
2026-11-04 14:38:19 +08:00
parent c633789a92
commit 3e7719f200
19 changed files with 305 additions and 3378 deletions
+56 -8
View File
@@ -1011,7 +1011,8 @@ func (db *users) BanUserAndCleanup(ctx context.Context, userID uint) error {
}
// purgeUserContent 物理删除某用户的全部内容数据:追问、消息与消息图片、私聊会话、
// 提问-图片关联、提问、拉黑关系、功能开关。**不含用户行本身**(由调用方决定是软删除还是物理删除)。
// 提问图片元数据及关联、提问、投票及其子表、访问统计、拉黑关系、功能开关。
// **不含用户行本身**(由调用方决定是软删除还是物理删除),也不删除审计/处置记录。
//
// 全部使用 Unscoped 物理删除而非软删除:这些表多为软删除模型,而部分表的唯一索引
// 不含 deleted_at(如 conversations.token),软删除会让被删行永久占据唯一槽位。
@@ -1027,18 +1028,18 @@ func purgeUserContent(ctx context.Context, tx *gorm.DB, userID uint) error {
return errors.Wrap(err, "delete follow ups of user")
}
// 消息图片必须先于消息删除,否则消息子查询将无法再匹配待删图片。
if err := tx.WithContext(ctx).Unscoped().
Where("message_id IN (SELECT id FROM messages WHERE conversation_id IN (SELECT id FROM conversations WHERE owner_id = ? OR asker_id = ?))", userID, userID).
Delete(&MessageImage{}).Error; err != nil {
return errors.Wrap(err, "delete message images of user")
}
// 消息:属于该用户作为 owner 或 asker 的会话
if err := tx.WithContext(ctx).Unscoped().
Where("conversation_id IN (SELECT id FROM conversations WHERE owner_id = ? OR asker_id = ?)", userID, userID).
Delete(&Message{}).Error; err != nil {
return errors.Wrap(err, "delete messages of user")
}
// 消息图片
if err := tx.WithContext(ctx).Unscoped().
Where("message_id IN (SELECT id FROM messages WHERE conversation_id IN (SELECT id FROM conversations WHERE owner_id = ? OR asker_id = ?))", userID, userID).
Delete(&MessageImage{}).Error; err != nil {
return errors.Wrap(err, "delete message images of user")
}
// 私聊会话
if err := tx.WithContext(ctx).Unscoped().
@@ -1047,7 +1048,25 @@ func purgeUserContent(ctx context.Context, tx *gorm.DB, userID uint) error {
return errors.Wrap(err, "delete conversations of user")
}
// 提问-图片关联(仅删除关联记录;UploadImage 本体为通用资源,保留)
// 提问图片元数据:删除本用户上传的孤立图片,以及仅链接到本用户提问的图片。
// 仍被其他提问引用的图片必须保留;这里只清数据库元数据,不操作对象存储。
if err := tx.WithContext(ctx).Unscoped().
Where(`(
uploader_user_id = ?
OR upload_images.id IN (
SELECT upload_image_id FROM upload_image_questions
WHERE question_id IN (SELECT id FROM questions WHERE user_id = ?)
)
) AND NOT EXISTS (
SELECT 1 FROM upload_image_questions AS shared_links
WHERE shared_links.upload_image_id = upload_images.id
AND shared_links.question_id NOT IN (SELECT id FROM questions WHERE user_id = ?)
)`, userID, userID, userID).
Delete(&UploadImage{}).Error; err != nil {
return errors.Wrap(err, "delete upload images of user")
}
// 提问-图片关联
if err := tx.WithContext(ctx).Unscoped().
Where("question_id IN (SELECT id FROM questions WHERE user_id = ?)", userID).
Delete(&UploadImageQuestion{}).Error; err != nil {
@@ -1061,6 +1080,35 @@ func purgeUserContent(ctx context.Context, tx *gorm.DB, userID uint) error {
return errors.Wrap(err, "delete questions of user")
}
// 投票子表必须先于投票主表删除;只处理该用户拥有的投票,不按参与者身份扩大范围。
if err := tx.WithContext(ctx).Unscoped().
Where("vote_id IN (SELECT id FROM votes WHERE user_id = ?)", userID).
Delete(&VoteSubmission{}).Error; err != nil {
return errors.Wrap(err, "delete vote submissions of user")
}
if err := tx.WithContext(ctx).Unscoped().
Where("vote_id IN (SELECT id FROM votes WHERE user_id = ?)", userID).
Delete(&VoteFormField{}).Error; err != nil {
return errors.Wrap(err, "delete vote form fields of user")
}
if err := tx.WithContext(ctx).Unscoped().
Where("vote_id IN (SELECT id FROM votes WHERE user_id = ?)", userID).
Delete(&VoteOption{}).Error; err != nil {
return errors.Wrap(err, "delete vote options of user")
}
if err := tx.WithContext(ctx).Unscoped().
Where("user_id = ?", userID).
Delete(&Vote{}).Error; err != nil {
return errors.Wrap(err, "delete votes of user")
}
// 提问箱访问统计:user_id 明确指向箱主,不属于站方审计/处置记录。
if err := tx.WithContext(ctx).Unscoped().
Where("user_id = ?", userID).
Delete(&BoxView{}).Error; err != nil {
return errors.Wrap(err, "delete box views of user")
}
// 拉黑关系(该用户拉黑别人,或被别人拉黑)
if err := tx.WithContext(ctx).Unscoped().
Where("user_id = ? OR blocked_id = ?", userID, userID).