| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
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).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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!
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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).
|
| |
|
|
| |
Also removed internal/nostr package - now using northwest.io/nostr library.
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| |
|
|
|
| |
Replace ASCII art banner with SVG logo featuring the muxstr branding.
Update page title and background color to match brand identity.
|
| |
|
|
|
|
| |
Update module path from northwest.io/nostr-grpc to northwest.io/muxstr.
This includes updating all Go imports, protobuf definitions, generated
files, and documentation.
|
| |
|
|
|
|
|
|
|
|
| |
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).
|
| |
|
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
|
|
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!
|