From fa8b4bb939430ba9c48fb4d3ba2b0e9095e229d3 Mon Sep 17 00:00:00 2001 From: bndw Date: Sun, 15 Feb 2026 17:08:21 -0800 Subject: fix: move rate limiting before auth checks - Rate limiting now happens immediately after parsing - Prevents spam from wasting resources on auth validation - Protects against DoS from unauthenticated request floods - IP-based rate limits now apply to all spam, not just authenticated users --- internal/handler/websocket/handler.go | 46 ++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/internal/handler/websocket/handler.go b/internal/handler/websocket/handler.go index 909e2ec..c1b23b8 100644 --- a/internal/handler/websocket/handler.go +++ b/internal/handler/websocket/handler.go @@ -265,19 +265,8 @@ func (h *Handler) handleEvent(ctx context.Context, conn *websocket.Conn, raw []j return fmt.Errorf("invalid event: %w", err) } - if err := h.requireAuth(ctx, conn, true, state); err != nil { - status = "unauthorized" - h.sendOK(ctx, conn, event.ID, false, err.Error()) - return nil - } - - if state.authenticatedPubkey == "" && h.authConfig != nil && h.authConfig.WriteEnabled { - status = "unauthenticated" - h.sendOK(ctx, conn, event.ID, false, "auth-required: authentication required") - return nil - } - // Rate limiting - use pubkey if authenticated, otherwise IP + // Check this BEFORE auth to prevent spam from wasting resources if h.limiter != nil { identifier := state.authenticatedPubkey if identifier == "" { @@ -290,6 +279,18 @@ func (h *Handler) handleEvent(ctx context.Context, conn *websocket.Conn, raw []j } } + if err := h.requireAuth(ctx, conn, true, state); err != nil { + status = "unauthorized" + h.sendOK(ctx, conn, event.ID, false, err.Error()) + return nil + } + + if state.authenticatedPubkey == "" && h.authConfig != nil && h.authConfig.WriteEnabled { + status = "unauthenticated" + h.sendOK(ctx, conn, event.ID, false, "auth-required: authentication required") + return nil + } + // Event already parsed above for auth check if !event.CheckID() { @@ -376,17 +377,8 @@ func (h *Handler) handleReq(ctx context.Context, conn *websocket.Conn, raw []jso return fmt.Errorf("REQ expects at least 3 elements") } - if err := h.requireAuth(ctx, conn, false, state); err != nil { - status = "unauthorized" - return err - } - - if state.authenticatedPubkey == "" && h.authConfig != nil && h.authConfig.ReadEnabled { - status = "unauthenticated" - return nil - } - // Rate limiting - use pubkey if authenticated, otherwise IP + // Check this BEFORE auth to prevent spam from wasting resources if h.limiter != nil { identifier := state.authenticatedPubkey if identifier == "" { @@ -398,6 +390,16 @@ func (h *Handler) handleReq(ctx context.Context, conn *websocket.Conn, raw []jso } } + if err := h.requireAuth(ctx, conn, false, state); err != nil { + status = "unauthorized" + return err + } + + if state.authenticatedPubkey == "" && h.authConfig != nil && h.authConfig.ReadEnabled { + status = "unauthenticated" + return nil + } + var subID string if err := json.Unmarshal(raw[1], &subID); err != nil { status = "error" -- cgit v1.2.3