summaryrefslogtreecommitdiffstats
path: root/internal/handler/websocket/handler.go
Commit message (Collapse)AuthorAgeFilesLines
* refactor: migrate nostr dependency to code.northwest.iobndw18 hours1-1/+1
| | | | Updated all import references from northwest.io/nostr to code.northwest.io/nostr and removed the local replace directive from go.mod. The module is now resolved from the published repository.
* fix: track rate limit hits in metricsbndw37 hours1-0/+7
| | | | | | | - Added RecordRateLimitHit to MetricsRecorder interface - Call RecordRateLimitHit when EVENT or REQ is rate limited - Allows dashboard to show accurate rate limiting statistics - Previously rate limits were applied but not tracked
* fix: move rate limiting before auth checksbndw37 hours1-22/+24
| | | | | | | - 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
* feat: track authorized (authenticated + successful) requestsbndw44 hours1-2/+12
| | | | | | | | | | | | | | | | | | | | | Add 'authorized' status for requests that complete successfully after authentication. This complements the existing 'unauthenticated' (pre-auth) status tracking. Now the dashboard shows: - Authorized: Authenticated requests that succeeded - Unauthorized: Authenticated requests rejected (not in allowlist) - Pre-Auth: Requests sent before authentication This gives full visibility into the auth flow: 1. Challenges: How many clients authenticated 2. Authorized: How many authenticated requests succeeded 3. Unauthorized: How many were rejected despite valid auth 4. Pre-Auth: How many tried before authenticating Updated metrics: - requests_total{status="authorized"} - authenticated successes - requests_total{status="ok"} - unauthenticated successes (when no auth)
* fix: record AUTH attempt metrics in WebSocket handlerbndw44 hours1-0/+9
| | | | | | | | | | | | Add RecordAuthAttempt calls to handleAuth so successful and failed AUTH attempts are tracked in metrics. This fixes the dashboard 'Challenges' counter which was always showing 0. The deferred call ensures both success and failure cases are recorded: - success=true when AUTH completes successfully - success=false when AUTH fails (invalid signature, wrong challenge, etc.) Updated MetricsRecorder interface and mock to include RecordAuthAttempt.
* feat: track auth rejections with specific 'unauthorized' statusbndw44 hours1-2/+2
| | | | | | | | | | | | | | | Auth failures (pubkey not in allowlist) are now tracked with status 'unauthorized' instead of generic 'error' in metrics. This allows monitoring of auth rejections separately from other errors. Metrics will now show: - muxstr_relay_requests_total{status="unauthorized"} - auth failures - muxstr_relay_requests_total{status="unauthenticated"} - no auth yet - muxstr_relay_requests_total{status="error"} - other errors - muxstr_relay_requests_total{status="rate_limited"} - rate limited - muxstr_relay_requests_total{status="ok"} - success Added test assertion to verify metrics tracking.
* fix: improve WebSocket error responses and protocol compliancebndw44 hours1-8/+10
| | | | | | | | | | | | | - 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.
* fix: send OK response when AUTH required but not completedbndw45 hours1-5/+9
| | | | | | | | | | | | | Previously when an EVENT was received before authentication was completed, the relay would send an AUTH challenge but silently drop the EVENT without sending an OK response. This caused clients to timeout waiting for the OK that never came. Now the relay immediately sends OK false with "auth-required" message, so clients know the EVENT was rejected and can retry after AUTH completes. Also optimized to parse the event JSON only once instead of twice.
* feat: add metrics for blocked eventsbndw46 hours1-0/+4
| | | | | | | | | | | | Track and display blocked event counts with per-kind breakdown. - Add events_blocked_total counter with kind label - Add RecordBlockedEvent method to metrics - Display blocked count in dashboard Storage section - Call metric when rejecting non-core protocol kinds Allows monitoring spam patterns and verifying kind filter effectiveness. Raw metrics show breakdown by kind (e.g., kind=20001, kind=30311).
* feat: filter events to core Nostr protocol kinds onlybndw46 hours1-0/+42
| | | | | | | | | | | | | | | | | | | | Add allowlist filtering to reject spam, ephemeral events, and live chat messages. Only accept core protocol kinds (notes, reactions, metadata, etc). Allowed kinds: - 0, 1, 3, 4, 5, 6, 7: Core protocol (NIP-01, 02, 04, 09, 18, 25) - 9735: Zaps (NIP-57) - 10000-10002: Mute/pin lists (NIP-51) - 10050: Relay list metadata - 30023, 30078: Long-form content, app data (NIP-23, 78) Rejected kinds: - 20001: Bot metadata spam (~157+ events/day) - 30311: Live chat messages (~100+ events/day) - All other kinds: Future spam/ephemeral events Reduces storage growth by ~80% while keeping all essential functionality. Clients receive "rejected: kind not supported" for filtered events.
* feat: track WebSocket requests in metricsbndw2 days1-0/+46
| | | | | | | | | | | | | Add request tracking for EVENT and REQ messages to match gRPC behavior. Dashboard now shows total/success/error counts for all requests. - Add RecordRequest to MetricsRecorder interface - Track timing and status in handleEvent and handleReq - Record metrics with status: ok, error, unauthenticated, rate_limited - Measure request duration for performance monitoring WebSocket is the primary interface, so tracking these requests is critical for understanding relay usage and performance.
* fix: track WebSocket connections in metricsbndw2 days1-0/+13
| | | | | | | | | | | Connection metrics were defined but never called. Add IncrementConnections and DecrementConnections to MetricsRecorder interface and call them when WebSocket connections are established/closed. - Add connection methods to MetricsRecorder interface - Increment on successful WebSocket upgrade - Decrement in defer when connection closes - Dashboard will now show active connection count
* feat: add rate limiting to WebSocket connectionsbndw2 days1-1/+53
| | | | | | | | | | | | | | | WebSocket clients were completely unprotected from abuse. Add RateLimiter interface to WebSocket handler and enforce limits on EVENT and REQ messages. - Add RateLimiter interface with Allow(identifier, method) method - Track client IP in connState (proxy-aware via X-Forwarded-For) - Check rate limits in handleEvent and handleReq - Use authenticated pubkey as identifier, fallback to IP - Share same rate limiter instance with gRPC - Add getClientIP() helper that checks proxy headers first Critical security fix for production deployment. Without this, any client could spam unlimited events/subscriptions via WebSocket.
* refactor: use AuthStore interface and remove type assertionsbndw3 days1-34/+51
| | | | | | | | | | | | | Replace runtime type assertions with compile-time safe AuthStore interface. Add connState struct for cleaner per-connection state management instead of mutable pointer parameters. Reduce auth challenge TTL from 10min to 2min. - Add AuthStore interface with CreateAuthChallenge and ValidateAndConsumeChallenge - Add connState struct for authenticatedPubkey and authChallenge - Remove fragile type assertion pattern in requireAuth and handleAuth - Add nil checks for auth store with clear error messages - Update Handler to have separate auth field - Wire auth store in main.go when auth is enabled
* fix: silently wait for auth instead of sending NOTICEbndw3 days1-1/+9
| | | | | | | After sending AUTH challenge, return nil instead of error to avoid sending NOTICE messages to clients. Add explicit checks in handleEvent and handleReq to silently ignore requests when auth is required but client hasn't authenticated yet. This follows NIP-42 spec more closely.
* feat: implement NIP-42 WebSocket authenticationbndw3 days1-6/+126
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Add support for authenticating WebSocket clients using NIP-42 protocol, enabling auth restrictions for normal Nostr clients. Storage layer (internal/storage/auth.go): - CreateAuthChallenge() - Generate random 32-byte challenge with 10min TTL - ValidateAndConsumeChallenge() - Verify challenge validity and mark as used - CleanupExpiredChallenges() - Remove old challenges from database - Uses existing auth_challenges table WebSocket handler (internal/handler/websocket/handler.go): - Track authenticatedPubkey per connection - Track authChallenge per connection - requireAuth() - Check if operation requires authentication - handleAuth() - Process AUTH responses (kind 22242 events) - sendAuthChallenge() - Send AUTH challenge to client - Enforce auth on EVENT (writes) and REQ (reads) messages - Support separate read/write allowlists Main (cmd/relay/main.go): - Wire auth config from YAML to WebSocket handler - Pass read/write enabled flags and allowed npub lists NIP-42 Flow: 1. Client sends EVENT/REQ without auth 2. If auth required, relay sends: ["AUTH", "<challenge>"] 3. Client signs kind 22242 event with challenge tag 4. Client sends: ["AUTH", <signed-event>] 5. Relay validates signature, challenge, and allowlist 6. Connection marked as authenticated 7. Client can now EVENT/REQ Example config to restrict writes to your npub: ```yaml auth: write: enabled: true allowed_npubs: - npub1your-npub-here... ``` WebSocket clients (Damus, Amethyst, etc.) can now authenticate!
* feat: track WebSocket subscriptions in metricsbndw3 days1-0/+27
| | | | | | | | | | | | | | | | | Add metrics tracking for WebSocket (NIP-01) subscriptions in addition to existing gRPC subscription tracking. Changes: - Add Count() method to subscription.Manager - Add MetricsRecorder interface to WebSocket handler - Update subscription metrics when REQ/CLOSE messages processed - Wire up metrics to WebSocket handler in main.go Before: Only gRPC stream subscriptions were counted After: Both gRPC and WebSocket subscriptions tracked accurately This fixes the dashboard showing 0 subscriptions when clients connect via WebSocket (e.g., nak req --stream).
* refactor: remove frivolous comments from auth validation/credentialsbndw3 days1-1/+1
| | | | Also removed internal/nostr package - now using northwest.io/nostr library.
* refactor: rename project from nostr-grpc to muxstrbndw3 days1-5/+5
| | | | | | Update module path from northwest.io/nostr-grpc to northwest.io/muxstr. This includes updating all Go imports, protobuf definitions, generated files, and documentation.
* fix: allow WebSocket upgrades to bypass HTML indexbndw3 days1-2/+3
| | | | | | | | | | WebSocket connections start as GET requests with 'Upgrade: websocket' header. The handler was serving HTML for ALL GET requests, preventing WebSocket upgrades from ever happening. Fix by checking for Upgrade header and only serving HTML/NIP-11 for non-WebSocket GET requests. Now WebSocket connections return status 101 (Switching Protocols) instead of 200 (OK).
* feat: add HTML index page for browser viewingbndw3 days1-4/+24
| | | | | | | | | | Add a beautiful HTML landing page when visiting relay in browser: - Shows all three protocol endpoints (gRPC, Connect, WebSocket) - Lists supported NIPs (01, 09, 11) - Displays relay features and info - Responsive design with gradient styling - Serves on GET requests (regular Accept header) - NIP-11 still served for Accept: application/nostr+json
* feat: implement NIP-09 with hard deletebndw3 days1-0/+11
| | | | | | | | | | | Implement event deletion (NIP-09) using hard delete approach: - Kind 5 events trigger deletion but are not stored themselves - ProcessDeletion hard deletes referenced events (DELETE FROM events) - Only authors can delete their own events (pubkey verification) - Support multiple event IDs in single deletion request - No deletions table needed (simpler schema) - Added 4 deletion tests covering various scenarios - All 45 tests passing
* refactor: simplify deletion handling (remove NIP-09 processing)bndw3 days1-8/+0
| | | | | | | | | | Remove deletion processing logic in favor of simpler approach: - Remove deletions table from schema - Delete deletions.go and deletions_test.go - Remove ProcessDeletion from EventStore interface - Kind 5 events now stored like any other event (no special handling) - Update storage test to expect 2 tables instead of 3 - All 41 tests passing
* feat: implement NIP-09 (deletions) and NIP-11 (relay info)bndw3 days1-0/+13
| | | | | | | | | | | | | | | | | | | | | | NIP-11 (Relay Information Document): - Serves relay metadata at GET / with Accept: application/nostr+json - Returns name, description, supported NIPs, limitations - CORS headers for browser compatibility NIP-09 (Event Deletion): - Kind 5 events delete events referenced in 'e' tags - Only authors can delete their own events - Soft delete (marks deleted=1) - Records deletion in deletions table - Works across all protocols (gRPC, Connect, WebSocket) Fixed deletions schema: - deleted_event_id as PRIMARY KEY (not deletion_event_id) - Allows one deletion event to delete multiple events 3 new tests, 44 total tests passing Supported NIPs now: 1, 9, 11
* feat: add WebSocket server with full NIP-01 supportbndw4 days1-0/+246
WebSocket handler: - NIP-01 protocol (EVENT, REQ, CLOSE, OK, EOSE, NOTICE) - JSON envelope parsing - Shares subscription manager with gRPC (unified event fan-out) - Standard Nostr client compatibility Relay now serves dual protocols: - gRPC on :50051 (binary, high performance) - WebSocket on :8080 (JSON, Nostr standard) Both protocols share: - Same storage layer - Same subscription manager - Same validation logic Compatible with all Nostr clients!