Security hardening
- JWT: validate signing algorithm (prevent alg confusion) - Login: rate limiting (10 attempts per 5 min per IP) - Request body: 10MB size limit (prevent DoS) - WebSocket: require JWT auth (token query param or cookie) - Daemon endpoints: require admin role (not just any user) - io.LimitReader on all request body decoding
This commit is contained in:
+38
-1
@@ -2,11 +2,13 @@ package collab
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
@@ -61,7 +63,26 @@ func (h *Hub) removeClient(fileID string, conn *websocket.Conn) {
|
||||
|
||||
// HandleWebSocket handles the Yjs WebSocket sync protocol.
|
||||
// Clients send binary Yjs update messages; the hub broadcasts to all others in the room.
|
||||
func (h *Hub) HandleWebSocket(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *Hub) HandleWebSocket(w http.ResponseWriter, r *http.Request, secret string) {
|
||||
// Authenticate via query param or cookie
|
||||
tokenStr := r.URL.Query().Get("token")
|
||||
if tokenStr == "" {
|
||||
if c, err := r.Cookie("authToken"); err == nil {
|
||||
tokenStr = c.Value
|
||||
}
|
||||
}
|
||||
if tokenStr == "" {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
// Validate token (import auth inline to avoid circular — just parse JWT here)
|
||||
// We accept any valid token
|
||||
_, _, err := parseToken(tokenStr, secret)
|
||||
if err != nil {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract file ID from path: /ws/collab/{fileID}
|
||||
path := r.URL.Path
|
||||
parts := strings.Split(strings.TrimPrefix(path, "/ws/collab/"), "/")
|
||||
@@ -131,3 +152,19 @@ func (h *Hub) ActiveUsers(fileID string) int {
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func parseToken(tokenStr, secret string) (string, bool, error) {
|
||||
token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
|
||||
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method")
|
||||
}
|
||||
return []byte(secret), nil
|
||||
})
|
||||
if err != nil || !token.Valid {
|
||||
return "", false, err
|
||||
}
|
||||
claims := token.Claims.(jwt.MapClaims)
|
||||
userID, _ := claims["sub"].(string)
|
||||
isAdmin, _ := claims["admin"].(bool)
|
||||
return userID, isAdmin, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user