diff options
| author | bndw <ben@bdw.to> | 2026-02-15 10:06:26 -0800 |
|---|---|---|
| committer | bndw <ben@bdw.to> | 2026-02-15 10:06:26 -0800 |
| commit | ae95a3fd20b5dc8c25ee947f5cc8e120a185b8bc (patch) | |
| tree | d8df895e4480a3416b0e6a2a46f723afc8bd00e9 | |
| parent | d744c32f1bc7411e04c97a9d14c172baaa0e4a89 (diff) | |
fix: improve WebSocket error responses and protocol compliance
- Send OK false for rate limit errors instead of NOTICE
- Send OK false for auth errors (e.g. pubkey not in allowlist)
- Remove OK response for AUTH events (AUTH is not an EVENT type)
- Parse event before auth checks to get event ID for error responses
These changes improve client UX by providing immediate, structured
feedback for all rejection cases instead of generic NOTICE messages.
The AUTH event OK removal fixes a bug where clients would read the
wrong response when sending EVENT after AUTH.
| -rw-r--r-- | internal/handler/websocket/handler.go | 18 |
1 files changed, 10 insertions, 8 deletions
diff --git a/internal/handler/websocket/handler.go b/internal/handler/websocket/handler.go index 5201698..a23dd60 100644 --- a/internal/handler/websocket/handler.go +++ b/internal/handler/websocket/handler.go | |||
| @@ -257,18 +257,19 @@ func (h *Handler) handleEvent(ctx context.Context, conn *websocket.Conn, raw []j | |||
| 257 | return fmt.Errorf("EVENT expects 2 elements") | 257 | return fmt.Errorf("EVENT expects 2 elements") |
| 258 | } | 258 | } |
| 259 | 259 | ||
| 260 | if err := h.requireAuth(ctx, conn, true, state); err != nil { | 260 | // Parse event first to get ID for error responses |
| 261 | status = "error" | ||
| 262 | return err | ||
| 263 | } | ||
| 264 | |||
| 265 | // Parse event to get ID for OK response | ||
| 266 | var event nostr.Event | 261 | var event nostr.Event |
| 267 | if err := json.Unmarshal(raw[1], &event); err != nil { | 262 | if err := json.Unmarshal(raw[1], &event); err != nil { |
| 268 | status = "error" | 263 | status = "error" |
| 269 | return fmt.Errorf("invalid event: %w", err) | 264 | return fmt.Errorf("invalid event: %w", err) |
| 270 | } | 265 | } |
| 271 | 266 | ||
| 267 | if err := h.requireAuth(ctx, conn, true, state); err != nil { | ||
| 268 | status = "error" | ||
| 269 | h.sendOK(ctx, conn, event.ID, false, err.Error()) | ||
| 270 | return nil | ||
| 271 | } | ||
| 272 | |||
| 272 | if state.authenticatedPubkey == "" && h.authConfig != nil && h.authConfig.WriteEnabled { | 273 | if state.authenticatedPubkey == "" && h.authConfig != nil && h.authConfig.WriteEnabled { |
| 273 | status = "unauthenticated" | 274 | status = "unauthenticated" |
| 274 | h.sendOK(ctx, conn, event.ID, false, "auth-required: authentication required") | 275 | h.sendOK(ctx, conn, event.ID, false, "auth-required: authentication required") |
| @@ -283,7 +284,8 @@ func (h *Handler) handleEvent(ctx context.Context, conn *websocket.Conn, raw []j | |||
| 283 | } | 284 | } |
| 284 | if !h.limiter.Allow(identifier, "EVENT") { | 285 | if !h.limiter.Allow(identifier, "EVENT") { |
| 285 | status = "rate_limited" | 286 | status = "rate_limited" |
| 286 | return fmt.Errorf("rate limit exceeded") | 287 | h.sendOK(ctx, conn, event.ID, false, "rate-limited: slow down") |
| 288 | return nil | ||
| 287 | } | 289 | } |
| 288 | } | 290 | } |
| 289 | 291 | ||
| @@ -558,7 +560,7 @@ func (h *Handler) handleAuth(ctx context.Context, conn *websocket.Conn, raw []js | |||
| 558 | state.authenticatedPubkey = authEvent.PubKey | 560 | state.authenticatedPubkey = authEvent.PubKey |
| 559 | log.Printf("WebSocket client authenticated: %s", authEvent.PubKey[:16]) | 561 | log.Printf("WebSocket client authenticated: %s", authEvent.PubKey[:16]) |
| 560 | 562 | ||
| 561 | h.sendOK(ctx, conn, authEvent.ID, true, "") | 563 | // Don't send OK for AUTH - it's not an EVENT |
| 562 | return nil | 564 | return nil |
| 563 | } | 565 | } |
| 564 | 566 | ||
