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:
2026-05-26 22:51:33 +02:00
parent 2de92b0375
commit 4f3113199b
5 changed files with 90 additions and 7 deletions
+38 -1
View File
@@ -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
}