diff --git a/internal/rpc/rtc/signal.go b/internal/rpc/rtc/signal.go index 0aa131dc7..b20e02d36 100644 --- a/internal/rpc/rtc/signal.go +++ b/internal/rpc/rtc/signal.go @@ -77,7 +77,6 @@ func (s *rtcServer) SignalMessageAssemble(ctx context.Context, req *rtc.SignalMe resp.Payload = &rtc.SignalResp_GetTokenByRoomID{GetTokenByRoomID: r} respErr = err default: - // Fix P0: 原代码在此调用 respErr.Error(),而 respErr 为 nil,会直接 panic return nil, errs.ErrArgs.WrapMsg("unknown signal payload type") } if respErr != nil { @@ -94,12 +93,10 @@ func (s *rtcServer) handleInvite(ctx context.Context, req *rtc.SignalInviteReq, log.ZError(ctx, "handleInvite", errs.ErrArgs, "r", "invitation is nil") return nil, errs.ErrArgs.WrapMsg("invitation is nil") } - // Fix P3: RoomID 统一由服务端生成,忽略客户端传入的值(客户端不应决定 RoomID) inv.RoomID = newRoomID() inv.InviterUserID = req.UserID inv.InitiateTime = time.Now().UnixMilli() - // 校验每位被邀请者的通话接受权限,1-to-1 场景:有任一被邀请者拒绝则直接返错 for _, inviteeID := range inv.InviteeUserIDList { allowed, err := s.isCallAllowed(ctx, req.UserID, inviteeID) if err != nil { @@ -118,17 +115,12 @@ func (s *rtcServer) handleInvite(ctx context.Context, req *rtc.SignalInviteReq, token, err := s.genToken(inv.RoomID, req.UserID) if err != nil { - // LiveKit Room 已创建,需要回滚 if _, delErr := s.roomClient.DeleteRoom(ctx, &livekit.DeleteRoomRequest{Room: inv.RoomID}); delErr != nil { log.ZWarn(ctx, "handleInvite: rollback DeleteRoom failed", delErr, "roomID", inv.RoomID) } return nil, err } - // Fix P1/幂等: CreateInvitation 失败分两种情况: - // - 重复 key(相同 roomID 重试)→ 认为幂等成功,直接返回 - // - 其他错误 → 回滚 LiveKit Room,返回错误 - // Fix P0: 原代码对失败仅打 warn,导致 DB 无记录、Room 泄漏、后续流程断裂 if err := s.db.CreateInvitation(ctx, invitationToModel(inv, req.OfflinePushInfo)); err != nil { if mongo.IsDuplicateKeyError(err) { log.ZWarn(ctx, "handleInvite: duplicate invitation (idempotent retry)", err, "roomID", inv.RoomID) @@ -144,7 +136,7 @@ func (s *rtcServer) handleInvite(ctx context.Context, req *rtc.SignalInviteReq, if err != nil { return nil, err } - // Fix P1: 1v1 场景下,通知失败应返回错误(被叫收不到来电意味着主叫白等) + for _, inviteeID := range inv.InviteeUserIDList { log.ZInfo(ctx, "sendSignalingNotification to invitee", "sendID", req.UserID, "recvID", inviteeID) if err := s.sendSignalingNotification(ctx, req.UserID, inviteeID, int32(constant.SingleChatType), req.OfflinePushInfo, content); err != nil { @@ -167,7 +159,7 @@ func (s *rtcServer) handleInviteInGroup(ctx context.Context, req *rtc.SignalInvi if inv == nil { return nil, errs.ErrArgs.WrapMsg("invitation is nil") } - // Fix P3: RoomID 统一由服务端生成 + inv.RoomID = newRoomID() inv.InviterUserID = req.UserID inv.InitiateTime = time.Now().UnixMilli() @@ -184,7 +176,6 @@ func (s *rtcServer) handleInviteInGroup(ctx context.Context, req *rtc.SignalInvi return nil, err } - // Fix P0: CreateInvitation 失败需要回滚 LiveKit Room if err := s.db.CreateInvitation(ctx, invitationToModel(inv, req.OfflinePushInfo)); err != nil { if !mongo.IsDuplicateKeyError(err) { if _, delErr := s.roomClient.DeleteRoom(ctx, &livekit.DeleteRoomRequest{Room: inv.RoomID}); delErr != nil { @@ -245,9 +236,6 @@ func (s *rtcServer) isCallAllowed(ctx context.Context, inviterID, inviteeID stri } } -// handleAccept processes a call acceptance. -// Fix P1(安全): 原代码完全信任客户端传入的 Invitation,未从 DB 校验邀请真实存在。 -// 攻击者可伪造任意 RoomID/InviterUserID 来获取 LiveKit Token 并加入房间。 func (s *rtcServer) handleAccept(ctx context.Context, req *rtc.SignalAcceptReq, signalReq *rtc.SignalReq) (*rtc.SignalAcceptResp, error) { if req.Invitation == nil { return nil, errs.ErrArgs.WrapMsg("invitation is nil") @@ -276,12 +264,11 @@ func (s *rtcServer) handleAccept(ctx context.Context, req *rtc.SignalAcceptReq, if err != nil { return nil, err } - // 使用 DB 中的 InviterUserID,防止客户端伪造通知目标 + if err := s.sendSignalingNotification(ctx, req.UserID, dbInv.InviterUserID, sessionType, req.OfflinePushInfo, content); err != nil { log.ZWarn(ctx, "sendSignalingNotification accept to inviter failed", err, "inviterID", dbInv.InviterUserID) } - // Fix P2: 1v1 通话接受后删除邀请记录,避免冷启动时重复弹出已接通的来电 // TODO: 群通话可通过 RemoveInvitee 实现精细化状态管理 if dbInv.GroupID == "" { if err := s.db.DeleteInvitation(ctx, dbInv.RoomID); err != nil { @@ -297,7 +284,6 @@ func (s *rtcServer) handleAccept(ctx context.Context, req *rtc.SignalAcceptReq, } // handleReject processes a call rejection. -// Fix P1(安全): 从 DB 验证邀请存在,并使用 DB 中的 InviterUserID,防止客户端伪造通知目标。 func (s *rtcServer) handleReject(ctx context.Context, req *rtc.SignalRejectReq, signalReq *rtc.SignalReq) (*rtc.SignalRejectResp, error) { if req.Invitation == nil { return nil, errs.ErrArgs.WrapMsg("invitation is nil") @@ -337,7 +323,6 @@ func (s *rtcServer) handleReject(ctx context.Context, req *rtc.SignalRejectReq, } // handleCancel processes a call cancellation. -// Fix P1(安全): 从 DB 验证操作者是邀请发起方,防止被叫方冒充取消通话。 func (s *rtcServer) handleCancel(ctx context.Context, req *rtc.SignalCancelReq, signalReq *rtc.SignalReq) (*rtc.SignalCancelResp, error) { if req.Invitation == nil { return nil, errs.ErrArgs.WrapMsg("invitation is nil") @@ -373,7 +358,6 @@ func (s *rtcServer) handleCancel(ctx context.Context, req *rtc.SignalCancelReq, } // handleHungUp processes a call hang-up. -// Fix P1(安全): 从 DB 验证操作者是通话参与者,防止任意用户挂断他人通话并删除 LiveKit Room。 func (s *rtcServer) handleHungUp(ctx context.Context, req *rtc.SignalHungUpReq, signalReq *rtc.SignalReq) (*rtc.SignalHungUpResp, error) { if req.Invitation == nil { return nil, errs.ErrArgs.WrapMsg("invitation is nil") @@ -415,7 +399,6 @@ func (s *rtcServer) handleHungUp(ctx context.Context, req *rtc.SignalHungUpReq, } // handleGetTokenByRoomID returns a LiveKit token for an existing room. -// Fix P0(安全): 原代码无权限校验,任意已登录用户可获取任意房间的 Token,可窃听他人通话。 func (s *rtcServer) handleGetTokenByRoomID(ctx context.Context, req *rtc.SignalGetTokenByRoomIDReq) (*rtc.SignalGetTokenByRoomIDResp, error) { dbInv, err := s.db.GetInvitationByRoomID(ctx, req.RoomID) if err != nil { @@ -706,8 +689,7 @@ func invitationToModel(inv *rtc.InvitationInfo, push *sdkws.OfflinePushInfo) *mo InitiateTime: inv.InitiateTime, BusyLineUserIDList: inv.BusyLineUserIDList, CreateTime: now.UnixMilli(), - // Fix P1(TTL): 根据 Timeout 设置过期时间,配合 MongoDB TTL 索引自动清理 - ExpireAt: now.Add(time.Duration(inv.Timeout+30) * time.Second), + ExpireAt: now.Add(time.Duration(inv.Timeout+30) * time.Second), } if push != nil { m.OfflinePushTitle = push.Title @@ -738,7 +720,6 @@ func modelToInvitationInfo(m *model.SignalInvitation) *rtc.InvitationInfo { } // hungUpPeerIDsFromDB returns IDs that should receive hang-up notification, based on authoritative DB data. -// Fix P1(安全): 原 hungUpPeerIDs 使用客户端传入的 inv,改为使用从 DB 获取的记录。 func hungUpPeerIDsFromDB(inv *model.SignalInvitation, callerID string) []string { if callerID == inv.InviterUserID { return inv.InviteeUserIDList