Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion backend/cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (

"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"


)

func main() {
Expand Down Expand Up @@ -146,7 +148,8 @@ func setupRouter(cfg *config.Config) *gin.Engine {
log.Println("Admin routes registered")

// Debate spectator WebSocket handler (no auth required for anonymous spectators)
router.GET("/ws/debate/:debateID", DebateWebsocketHandler)
router.GET("/ws/debate/:debateID", websocket.TeamDebateWebsocketHandler)


return router
}
5 changes: 2 additions & 3 deletions backend/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ go 1.24
toolchain go1.24.4

require (
github.com/casbin/casbin/v2 v2.132.0
github.com/casbin/mongodb-adapter/v3 v3.7.0
github.com/gin-contrib/cors v1.7.2
github.com/gin-gonic/gin v1.10.0
github.com/golang-jwt/jwt/v5 v5.2.2
Expand All @@ -23,13 +25,10 @@ require (
cloud.google.com/go/auth v0.15.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
cloud.google.com/go/longrunning v0.5.7 // indirect
github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/casbin/casbin/v2 v2.132.0 // indirect
github.com/casbin/govaluate v1.3.0 // indirect
github.com/casbin/mongodb-adapter/v3 v3.7.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
Expand Down
3 changes: 1 addition & 2 deletions backend/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIi
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
Expand Down Expand Up @@ -62,6 +60,7 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
Expand Down
31 changes: 31 additions & 0 deletions backend/websocket/team_debate_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"arguehub/db"
"arguehub/models"

"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
Expand Down Expand Up @@ -243,3 +244,33 @@ func (c *TeamDebateClient) writePump() {
}
}
}
func TeamDebateWebsocketHandler(c *gin.Context) {
debateIDHex := c.Param("debateID")
teamIDHex := c.Query("teamId")
userIDHex := c.Query("userId")
isTeam1 := c.Query("isTeam1") == "true"

debateID, _ := primitive.ObjectIDFromHex(debateIDHex)
teamID, _ := primitive.ObjectIDFromHex(teamIDHex)
userID, _ := primitive.ObjectIDFromHex(userIDHex)
Comment on lines +248 to +255
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Add input validation and error handling for required parameters.

The handler extracts query parameters and converts them to ObjectIDs without validation or error handling. This creates several risks:

  1. Missing required parameters (teamId, userId) will result in empty strings being parsed, creating zero-value ObjectIDs
  2. Invalid ObjectID hex strings will silently fail conversion, also resulting in zero-value ObjectIDs
  3. Zero-value ObjectIDs could cause incorrect database lookups or matches with unintended documents

Apply this diff to add proper validation and error handling:

 func TeamDebateWebsocketHandler(c *gin.Context) {
     debateIDHex := c.Param("debateID")
     teamIDHex := c.Query("teamId")
     userIDHex := c.Query("userId")
     isTeam1 := c.Query("isTeam1") == "true"
+
+    // Validate required parameters
+    if debateIDHex == "" || teamIDHex == "" || userIDHex == "" {
+        c.JSON(400, gin.H{"error": "Missing required parameters: debateID, teamId, and userId"})
+        return
+    }
 
-    debateID, _ := primitive.ObjectIDFromHex(debateIDHex)
-    teamID, _ := primitive.ObjectIDFromHex(teamIDHex)
-    userID, _ := primitive.ObjectIDFromHex(userIDHex)
+    debateID, err := primitive.ObjectIDFromHex(debateIDHex)
+    if err != nil {
+        c.JSON(400, gin.H{"error": "Invalid debateID format"})
+        return
+    }
+    
+    teamID, err := primitive.ObjectIDFromHex(teamIDHex)
+    if err != nil {
+        c.JSON(400, gin.H{"error": "Invalid teamId format"})
+        return
+    }
+    
+    userID, err := primitive.ObjectIDFromHex(userIDHex)
+    if err != nil {
+        c.JSON(400, gin.H{"error": "Invalid userId format"})
+        return
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
debateIDHex := c.Param("debateID")
teamIDHex := c.Query("teamId")
userIDHex := c.Query("userId")
isTeam1 := c.Query("isTeam1") == "true"
debateID, _ := primitive.ObjectIDFromHex(debateIDHex)
teamID, _ := primitive.ObjectIDFromHex(teamIDHex)
userID, _ := primitive.ObjectIDFromHex(userIDHex)
debateIDHex := c.Param("debateID")
teamIDHex := c.Query("teamId")
userIDHex := c.Query("userId")
isTeam1 := c.Query("isTeam1") == "true"
// Validate required parameters
if debateIDHex == "" || teamIDHex == "" || userIDHex == "" {
c.JSON(400, gin.H{"error": "Missing required parameters: debateID, teamId, and userId"})
return
}
debateID, err := primitive.ObjectIDFromHex(debateIDHex)
if err != nil {
c.JSON(400, gin.H{"error": "Invalid debateID format"})
return
}
teamID, err := primitive.ObjectIDFromHex(teamIDHex)
if err != nil {
c.JSON(400, gin.H{"error": "Invalid teamId format"})
return
}
userID, err := primitive.ObjectIDFromHex(userIDHex)
if err != nil {
c.JSON(400, gin.H{"error": "Invalid userId format"})
return
}
🤖 Prompt for AI Agents
In backend/websocket/team_debate_handler.go around lines 248 to 255, the handler
currently converts params to ObjectIDs without validation causing zero-value IDs
on missing/invalid input; update the code to: verify debateID, teamId and userId
are present (non-empty), call primitive.ObjectIDFromHex for each and check the
returned error, and if any conversion fails return a 400 response (with a clear
message indicating which param is missing/invalid) instead of proceeding; do not
use the zero-value ObjectIDs for DB lookups and keep isTeam1 parsed from the
query with a safe default.


conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
return
}

client := &TeamDebateClient{
conn: conn,
send: make(chan []byte, 256),
debateID: debateID,
teamID: teamID,
userID: userID,
isTeam1: isTeam1,
}

teamDebateHub.register <- client

go client.writePump()
go client.readPump()
}
Comment on lines +247 to +275
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add authentication and authorization checks.

The handler doesn't verify that the requesting user has permission to join the debate or that they belong to the specified team. This could allow:

  • Unauthorized users to join debates
  • Users to impersonate other users by providing different userId parameters
  • Users to access debates they shouldn't be able to see

Consider implementing one of these approaches:

  1. Add JWT authentication: Extract and validate the user's JWT token, then verify the token's user ID matches the userId parameter.

  2. Verify team membership: Query the database to confirm the user belongs to the specified team and that team is participating in the debate.

  3. Apply AuthMiddleware: If the route should be protected, register it within an authenticated route group in main.go.

Example authorization check:

func TeamDebateWebsocketHandler(c *gin.Context) {
    // ... existing parameter extraction and validation ...
    
    // Verify user belongs to the team
    teamCollection := db.GetCollection("teams")
    var team models.Team
    err := teamCollection.FindOne(nil, bson.M{
        "_id": teamID,
        "members": bson.M{"$elemMatch": bson.M{"user_id": userID}},
    }).Decode(&team)
    if err != nil {
        c.JSON(403, gin.H{"error": "User is not a member of the specified team"})
        return
    }
    
    // Verify team is part of the debate
    debateCollection := db.GetCollection("team_debates")
    var debate models.TeamDebate
    err = debateCollection.FindOne(nil, bson.M{
        "_id": debateID,
        "$or": []bson.M{
            {"team1_id": teamID},
            {"team2_id": teamID},
        },
    }).Decode(&debate)
    if err != nil {
        c.JSON(403, gin.H{"error": "Team is not participating in this debate"})
        return
    }
    
    // ... rest of handler ...
}
🤖 Prompt for AI Agents
In backend/websocket/team_debate_handler.go around lines 247-275, the handler
upgrades to a websocket without authenticating or authorizing the requester; fix
by (1) validating and parsing the authenticated user (e.g., extract/verify JWT
or require the route to be behind AuthMiddleware) and ensure the token's user ID
matches the userId query parameter, (2) check the parsed ObjectIDs for errors
and return 400 on invalid IDs before attempting an upgrade, (3) query the DB to
verify the user is a member of the specified team, and (4) confirm that the team
is a participant in the given debate; if any check fails, return an appropriate
4xx response and do not call upgrader.Upgrade or register the client.


Loading