summaryrefslogtreecommitdiffstats
path: root/internal/handler
Commit message (Collapse)AuthorAgeFilesLines
* Fix source code link URLmainClawd11 hours1-1/+1
|
* Add source code link to index pageClawd11 hours1-1/+1
|
* refactor: migrate nostr dependency to code.northwest.iobndw18 hours5-5/+5
| | | | 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 hours2-0/+8
| | | | | | | - 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 hours2-2/+19
| | | | | | | | | | | | | | | | | | | | | 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 hours2-0/+10
| | | | | | | | | | | | 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 hours2-2/+9
| | | | | | | | | | | | | | | 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.
* test: add integration tests for NIP-42 AUTH and rate limitingbndw44 hours1-0/+526
| | | | | | | | | | | | | | | | | | | Add comprehensive WebSocket handler integration tests that verify: - NIP-42 authentication flow (auth required, challenge/response) - Allowlist enforcement (reject unauthorized pubkeys) - Rate limiting by IP address - Rate limiting by authenticated pubkey - No-auth mode works correctly These tests use real WebSocket connections and would have caught the AUTH timeout bug and other protocol issues. Tests cover: - TestAuthRequired: Verifies AUTH challenge sent, client authenticates, publish succeeds - TestAuthNotInAllowlist: Verifies pubkeys not in allowlist are rejected - TestRateLimitByIP: Verifies unauthenticated clients are rate limited by IP - TestRateLimitByPubkey: Verifies authenticated clients are rate limited by pubkey - TestNoAuthWhenDisabled: Verifies publishing works when auth is disabled
* 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 days5-5/+5
| | | | Also removed internal/nostr package - now using northwest.io/nostr library.
* index updatebndw3 days1-1/+1
|
* index updatebndw3 days1-4/+4
|
* Update index pagebndw3 days1-9/+1
|
* refactor: simplify footer text on index pagebndw3 days1-1/+1
|
* refactor: remove PGP signature block from index pagebndw3 days1-8/+0
|
* feat: add muxstr SVG logo to index pagebndw3 days1-11/+16
| | | | | Replace ASCII art banner with SVG logo featuring the muxstr branding. Update page title and background color to match brand identity.
* refactor: rename project from nostr-grpc to muxstrbndw3 days8-22/+22
| | | | | | 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).
* fix: remove duplicate protocol prefix in WebSocket URLbndw3 days1-1/+1
| | | | | | | | | | | The template was hardcoding 'ws://' prefix, but when using --public-url we were already passing 'wss://'. This caused the URL to display as 'ws://wss://domain/'. Fix by: - Removing 'ws://' prefix from template - Always including protocol in the variable (ws:// or wss://) - Also add http:// prefix for local development consistency
* redesign: early web minimalism + cypherpunkbndw3 days1-408/+115
| | | | | | | | | | | | | | | | | | | | | | | | | | | Complete aesthetic reversal - from cyber-brutalist to minimal: AESTHETIC: 1995 web document meets cypherpunks mailing list - Courier New system font (no web fonts) - Black on white for readability - Semantic HTML with minimal CSS - PGP signature blocks (cypherpunk heritage) - Classic underlined blue links - Horizontal rules for section breaks - Looks like a .txt file rendered as HTML PHILOSOPHY: Throwback to when the web was just documents. No animations, no grids, no gradients. Just semantic HTML, monospace type, and information. Fast loading, accessible, timeless. CYPHERPUNK TOUCHES: - PGP signature blocks - Hash fingerprints - Technical language - Cryptographic references - Feels like reading crypto mailing list archives Zero frameworks, zero build tools, maximum signal.
* redesign: cyber-brutalist terminal aestheticbndw3 days1-101/+367
| | | | | | | | | | | | | | | | | | | | | | | | | | Complete visual overhaul with bold conceptual direction: AESTHETIC: Cyber-brutalist terminal interface - JetBrains Mono monospace throughout - Deep black (#0a0e14) with cyan/green accents - ASCII art Nostr logo with glitch animation - Animated grid background (scrolling terminal feel) - Terminal-style status bar with pulse indicators - Protocol cards with scanning line effects - Information-dense but organized layout MOTION & EFFECTS: - Glitching ASCII logo animation - Scanning line on protocol cards - Pulsing status indicators - Animated grid background - Staggered fade-in on page load - Hover effects with glow DIFFERENTIATION: Feels like SSH into a relay node. Unapologetically technical, embracing Nostr's decentralized, cypherpunk ethos. Zero generic design patterns - full commitment to terminal aesthetic.
* feat: add HTML index page for browser viewingbndw3 days2-4/+218
| | | | | | | | | | 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 days2-0/+28
| | | | | | | | | | | 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 days2-19/+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 days3-0/+84
| | | | | | | | | | | | | | | | | | | | | | 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 Connect (gRPC over HTTP/JSON) supportbndw3 days1-0/+101
| | | | | | | | | | | | | | | | | | | | Connect integration: - Buf Connect codegen added to buf.gen.yaml - Connect handler wraps gRPC server - Serves on same port as WebSocket (:8080) - HTTP/2 with h2c for cleartext HTTP/2 Now serving THREE protocols: 1. gRPC (native) on :50051 - binary, high performance 2. Connect on :8080/nostr.v1.NostrRelay/* - HTTP/JSON, browser compatible 3. WebSocket on :8080/ - Nostr standard protocol All three protocols share: - Same storage layer - Same subscription manager - Same validation logic Browser-friendly! Call gRPC methods with fetch() or curl.
* feat: add WebSocket server with full NIP-01 supportbndw4 days3-0/+326
| | | | | | | | | | | | | | | | | | | 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!
* feat: implement Subscribe with real-time event streamingbndw4 days1-3/+71
| | | | | | | | | | | | | | | | | | | | Subscription manager: - Track active subscriptions across connections - Filter matching with full NIP-01 support - Event fan-out to matching subscribers Subscribe RPC: - Query stored events (past) - Stream them to client - Keep stream open for new events (real-time) - Auto-generate subscription ID if not provided PublishEvent now: - Stores event - Notifies all matching active subscriptions - Streams to gRPC clients in real-time 4 new tests, all 41 tests passing
* feat: add gRPC handler with event validation and publishingbndw4 days4-0/+592
Handler implementation: - EventStore interface (consumer-side) - Server with PublishEvent, QueryEvents, CountEvents, PublishBatch - pb.Event <-> nostr.Event conversion helpers - Signature and ID validation using existing nostr package - Canonical JSON generation for storage 9 tests passing