Changeset 0.24.3 (#159)
This commit is contained in:
@@ -120,6 +120,21 @@ func getUserID(c *gin.Context) string {
|
||||
return s
|
||||
}
|
||||
|
||||
// isSessionAuth returns true if the request is from a session participant (v0.24.3).
|
||||
func isSessionAuth(c *gin.Context) bool {
|
||||
return c.GetString("auth_type") == "session"
|
||||
}
|
||||
|
||||
// sessionCanAccessChannel validates that a session participant is authorized
|
||||
// for the given channel. The AuthOrSession middleware already verifies this,
|
||||
// but handlers call this as a defense-in-depth check.
|
||||
func sessionCanAccessChannel(c *gin.Context, channelID string) bool {
|
||||
if !isSessionAuth(c) {
|
||||
return false
|
||||
}
|
||||
return c.GetString("channel_id") == channelID
|
||||
}
|
||||
|
||||
// parsePagination reads page and per_page from query params with defaults.
|
||||
func parsePagination(c *gin.Context) (page, perPage, offset int) {
|
||||
page, _ = strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
|
||||
@@ -194,6 +194,10 @@ func (h *CompletionHandler) Complete(c *gin.Context) {
|
||||
}
|
||||
|
||||
channelID := req.ChannelID
|
||||
if channelID == "" {
|
||||
// v0.24.3: Workflow API routes have channel in URL param
|
||||
channelID = c.Param("id")
|
||||
}
|
||||
if channelID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "channel_id is required"})
|
||||
return
|
||||
@@ -201,13 +205,25 @@ func (h *CompletionHandler) Complete(c *gin.Context) {
|
||||
|
||||
userID := getUserID(c)
|
||||
|
||||
// Verify participation: check channel_participants first, fall back to
|
||||
// legacy channels.user_id ownership for direct channels.
|
||||
isParticipant, _ := h.stores.Channels.IsParticipant(c.Request.Context(), channelID, "user", userID)
|
||||
if !isParticipant {
|
||||
if !userOwnsChannel(c, channelID, userID) {
|
||||
// v0.24.3: Session participants are pre-validated by AuthOrSession middleware
|
||||
if isSessionAuth(c) {
|
||||
if !sessionCanAccessChannel(c, channelID) {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "session not authorized for this channel"})
|
||||
return
|
||||
}
|
||||
// Use session_id as effective identity for cursor/participant tracking.
|
||||
// persistMessage will record participant_type=user with this ID —
|
||||
// acceptable for v0.24.3; v0.25.0 can refine to participant_type=session.
|
||||
userID = c.GetString("session_id")
|
||||
} else {
|
||||
// Verify participation: check channel_participants first, fall back to
|
||||
// legacy channels.user_id ownership for direct channels.
|
||||
isParticipant, _ := h.stores.Channels.IsParticipant(c.Request.Context(), channelID, "user", userID)
|
||||
if !isParticipant {
|
||||
if !userOwnsChannel(c, channelID, userID) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── ai_mode guard (v0.23.1) ────────────────────────────────────────────────
|
||||
@@ -630,8 +646,10 @@ func (h *CompletionHandler) Complete(c *gin.Context) {
|
||||
// Budget enforcement and model allowlist only apply when the request draws
|
||||
// from a team or global provider. BYOK (personal scope) is the user's money
|
||||
// — no ceiling applies.
|
||||
// v0.24.3: Session participants bypass user-level budget/allowlist checks.
|
||||
// They use the workflow channel's allocated resources.
|
||||
role, _ := c.Get("role")
|
||||
if role != "admin" && providerScope != "personal" {
|
||||
if role != "admin" && providerScope != "personal" && !isSessionAuth(c) {
|
||||
// Token budget
|
||||
if h.stores.Usage != nil {
|
||||
budget, err := auth.ResolveTokenBudget(c.Request.Context(), h.stores, userID)
|
||||
|
||||
@@ -87,7 +87,13 @@ func (h *MessageHandler) ListMessages(c *gin.Context) {
|
||||
userID := getUserID(c)
|
||||
channelID := c.Param("id")
|
||||
|
||||
if !userOwnsChannel(c, channelID, userID) {
|
||||
// v0.24.3: Session participants are pre-validated by AuthOrSession middleware
|
||||
if isSessionAuth(c) {
|
||||
if !sessionCanAccessChannel(c, channelID) {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "session not authorized for this channel"})
|
||||
return
|
||||
}
|
||||
} else if !userOwnsChannel(c, channelID, userID) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -193,16 +199,31 @@ func (h *MessageHandler) CreateMessage(c *gin.Context) {
|
||||
userID := getUserID(c)
|
||||
channelID := c.Param("id")
|
||||
|
||||
if !userOwnsChannel(c, channelID, userID) {
|
||||
// v0.24.3: Session participants are pre-validated by AuthOrSession middleware
|
||||
if isSessionAuth(c) {
|
||||
if !sessionCanAccessChannel(c, channelID) {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "session not authorized for this channel"})
|
||||
return
|
||||
}
|
||||
} else if !userOwnsChannel(c, channelID, userID) {
|
||||
return
|
||||
}
|
||||
|
||||
// Use cursor for parent, compute sibling_index
|
||||
parentID, _ := getActiveLeaf(channelID, userID)
|
||||
// Sessions use the same cursor logic (empty userID = channel-level latest)
|
||||
effectiveUserID := userID
|
||||
if isSessionAuth(c) {
|
||||
effectiveUserID = c.GetString("session_id")
|
||||
}
|
||||
parentID, _ := getActiveLeaf(channelID, effectiveUserID)
|
||||
siblingIdx := nextSiblingIndex(channelID, parentID)
|
||||
|
||||
participantType := "user"
|
||||
participantID := userID
|
||||
if isSessionAuth(c) {
|
||||
participantType = "session"
|
||||
participantID = c.GetString("session_id")
|
||||
}
|
||||
if req.Role == "assistant" {
|
||||
participantType = "model"
|
||||
if req.Model != "" {
|
||||
|
||||
Reference in New Issue
Block a user