feat(auth): implement session-based token management with revocation support
Add Session model, SessionService, and SessionRepository to track user login sessions and enable token revocation on auth-critical events. - Introduce explicit TokenType (access/refresh) in JWT claims to prevent refresh token misuse via access token endpoints - Add SessionID field to JWT claims, enabling stateless JWT validation against revoked sessions - Replace legacy Auth middleware with RequireAuth/OptionalAuth pipeline that validates token type, account status, and session validity - Implement session revocation on password change, reset, user ban/inactive, and explicit logout - Add Principal cache with active invalidation for banned/role-changed users - Fix IDOR vulnerability: GetMessagesByCursor now validates currentUserID is conversation participant via GetParticipantStrict - Add group member visibility checks: announcements, group info, member list now require group membership - Simplify Casbin policy: remove g grouping, use r.sub == p.sub matcher with globMatch; user_roles table is single source of truth for roles - Add migration logic to clean legacy casbin g rules and migrate old p rules from path-style to admin/<domain> resource naming
This commit is contained in:
@@ -91,9 +91,16 @@ type chatServiceImpl struct {
|
||||
versionLogMaxGap int64
|
||||
pushWorker *PushWorker
|
||||
redisClient *redis.Client
|
||||
|
||||
// groupAbility 用于群消息发送前的群成员/禁言校验。
|
||||
// 可选依赖:为 nil 时跳过群能力校验(向后兼容旧测试构造),生产环境应注入。
|
||||
groupAbility GroupAbility
|
||||
}
|
||||
|
||||
// NewChatService 创建聊天服务
|
||||
//
|
||||
// groupAbility 可选注入:群消息发送前的成员/禁言校验由此完成。
|
||||
// 生产环境必须注入,否则群禁言规则无法生效。
|
||||
func NewChatService(
|
||||
repo repository.MessageRepository,
|
||||
userRepo repository.UserRepository,
|
||||
@@ -105,6 +112,7 @@ func NewChatService(
|
||||
seqBufferMgr *cache.SeqBufferManager,
|
||||
pushWorker *PushWorker,
|
||||
redisClient *redis.Client,
|
||||
groupAbility GroupAbility,
|
||||
versionLogRepo ...repository.ConversationVersionLogRepository,
|
||||
) ChatService {
|
||||
// 创建适配器
|
||||
@@ -131,6 +139,7 @@ func NewChatService(
|
||||
uploadService: uploadService,
|
||||
pushWorker: pushWorker,
|
||||
redisClient: redisClient,
|
||||
groupAbility: groupAbility,
|
||||
}
|
||||
|
||||
if len(versionLogRepo) > 0 && versionLogRepo[0] != nil {
|
||||
@@ -466,6 +475,14 @@ func (s *chatServiceImpl) SendMessage(ctx context.Context, senderID string, conv
|
||||
return nil, fmt.Errorf("failed to get participant: %w", err)
|
||||
}
|
||||
|
||||
// 群消息发送:校验发送者是否仍是群成员、是否被禁言、群是否全员禁言。
|
||||
// 私聊会话不参与该校验。
|
||||
if conv.Type == model.ConversationTypeGroup && conv.GroupID != nil && *conv.GroupID != "" && s.groupAbility != nil {
|
||||
if err := s.groupAbility.CanSendGroupMessage(ctx, senderID, *conv.GroupID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if s.uploadService != nil {
|
||||
if err := s.uploadService.ValidateChatMessageImageSegments(segments); err != nil {
|
||||
return nil, err
|
||||
@@ -1054,8 +1071,8 @@ func (s *chatServiceImpl) IsUserOnline(userID string) bool {
|
||||
// SaveMessage 仅保存消息到数据库,不发送实时推送
|
||||
// 适用于群聊等由调用方自行负责推送的场景
|
||||
func (s *chatServiceImpl) SaveMessage(ctx context.Context, senderID string, conversationID string, segments model.MessageSegments, replyToID *string) (*model.Message, error) {
|
||||
// 验证会话是否存在
|
||||
_, err := s.getConversation(ctx, conversationID)
|
||||
// 验证会话是否存在(保留 conv 用于群消息能力校验)
|
||||
conv, err := s.getConversation(ctx, conversationID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("会话不存在,请重新创建会话")
|
||||
@@ -1072,6 +1089,14 @@ func (s *chatServiceImpl) SaveMessage(ctx context.Context, senderID string, conv
|
||||
return nil, fmt.Errorf("failed to get participant: %w", err)
|
||||
}
|
||||
|
||||
// 群消息发送:校验发送者是否仍是群成员、是否被禁言、群是否全员禁言。
|
||||
// 私聊会话不参与该校验。
|
||||
if conv.Type == model.ConversationTypeGroup && conv.GroupID != nil && *conv.GroupID != "" && s.groupAbility != nil {
|
||||
if err := s.groupAbility.CanSendGroupMessage(ctx, senderID, *conv.GroupID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if s.uploadService != nil {
|
||||
if err := s.uploadService.ValidateChatMessageImageSegments(segments); err != nil {
|
||||
return nil, err
|
||||
|
||||
Reference in New Issue
Block a user