diff --git a/internal/server/handlers.go b/internal/server/handlers.go index 79069c3b..b11de1a9 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -1067,6 +1067,7 @@ type upsertChatSessionRequest struct { } // handleUpsertChatSession 注册或更新会话元数据(立项/成员加入时同步)。 +// 默认归属认证用户本人;项目管理员可为其他成员代注册(防水平越权)。 func (s *Server) handleUpsertChatSession(w http.ResponseWriter, r *http.Request) { if s.ctxMgr == nil { writeSafeError(w, r, http.StatusServiceUnavailable, "context manager not initialized") @@ -1086,9 +1087,32 @@ func (s *Server) handleUpsertChatSession(w http.ResponseWriter, r *http.Request) writeSafeError(w, r, http.StatusBadRequest, "missing session_id") return } - req.UserID = user.ID - summary, err := s.ctxMgr.EnsureChatSessionOwned(r.Context(), req.SessionID, req.UserID, req.ProjectID, req.Title) + ownerID := user.ID + if req.UserID > 0 && req.UserID != user.ID { + if req.ProjectID <= 0 { + writeJSONResponse(w, http.StatusBadRequest, map[string]string{ + "error_code": "ERR_INVALID_PARAM", + "message": "代注册他人会话时必须指定 project_id", + }) + return + } + can, err := s.authzSvc.CanManageProjectMembers(r.Context(), req.ProjectID, user.ID, user.RoleID) + if err != nil { + writeSafeError(w, r, http.StatusInternalServerError, err.Error()) + return + } + if !can { + writeJSONResponse(w, http.StatusForbidden, map[string]string{ + "error_code": "ERR_FORBIDDEN", + "message": "无权为其他用户注册项目会话", + }) + return + } + ownerID = req.UserID + } + + summary, err := s.ctxMgr.EnsureChatSessionOwned(r.Context(), req.SessionID, ownerID, req.ProjectID, req.Title) if err != nil { if errors.Is(err, ctxmgr.ErrSessionOwnerMismatch) { writeJSONResponse(w, http.StatusForbidden, map[string]string{ diff --git a/internal/server/project_members_handlers.go b/internal/server/project_members_handlers.go index e20328ba..a3eed9de 100644 --- a/internal/server/project_members_handlers.go +++ b/internal/server/project_members_handlers.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "log" "net/http" "strconv" @@ -128,9 +129,27 @@ func (s *Server) handleAddProjectMember(w http.ResponseWriter, r *http.Request, writeDutySyncError(w) return } + s.ensureMemberChatSession(r.Context(), projectID, req.UserID) writeJSONResponse(w, http.StatusCreated, map[string]bool{"success": true}) } +// ensureMemberChatSession 为项目成员补齐私有会话(sess-p{id}-u{uid}),失败仅记日志。 +func (s *Server) ensureMemberChatSession(ctx context.Context, projectID, memberUserID int64) { + if s == nil || s.ctxMgr == nil || projectID <= 0 || memberUserID <= 0 { + return + } + title := "" + if s.pwSvc != nil { + if proj, err := s.pwSvc.GetProject(ctx, projectID, 0); err == nil && proj != nil { + title = proj.Name + } + } + sessionID := fmt.Sprintf("sess-p%d-u%d", projectID, memberUserID) + if _, err := s.ctxMgr.EnsureChatSessionOwned(ctx, sessionID, memberUserID, projectID, title); err != nil { + log.Printf("[members] ensure chat session for project=%d user=%d failed: %v", projectID, memberUserID, err) + } +} + func (s *Server) handleUpdateProjectMember(w http.ResponseWriter, r *http.Request, user *auth.UserAccount, projectID int64) { if !s.requireProjectMemberManage(w, r, user, projectID) { return