<feed xmlns='http://www.w3.org/2005/Atom'>
<title>muxstr.git/cmd, branch main</title>
<subtitle>Nostr relay with gRPC, HTTP/JSON, and WebSocket interfaces</subtitle>
<id>https://code.northwest.io/muxstr.git/atom?h=main</id>
<link rel='self' href='https://code.northwest.io/muxstr.git/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://code.northwest.io/muxstr.git/'/>
<updated>2026-03-08T02:34:25+00:00</updated>
<entry>
<title>Make allowed event kinds configurable</title>
<updated>2026-03-08T02:34:25+00:00</updated>
<author>
<name>bndw</name>
<email>ben@bdw.to</email>
</author>
<published>2026-03-08T02:34:25+00:00</published>
<link rel='alternate' type='text/html' href='https://code.northwest.io/muxstr.git/commit/?id=4522797a0cf378cc44485ed77a01dee9643b6ebe'/>
<id>urn:sha1:4522797a0cf378cc44485ed77a01dee9643b6ebe</id>
<content type='text'>
Replaces hardcoded kind allowlist in isAllowedKind with a configurable
allowed_kinds list in relay config. Empty list allows all kinds. Default
matches previous hardcoded list. Wired through SetAllowedKinds on the
handler.
</content>
</entry>
<entry>
<title>NIP-11: make relay info configurable</title>
<updated>2026-02-22T22:08:54+00:00</updated>
<author>
<name>Clawd</name>
<email>ai@clawd.bot</email>
</author>
<published>2026-02-22T22:08:54+00:00</published>
<link rel='alternate' type='text/html' href='https://code.northwest.io/muxstr.git/commit/?id=5195d8031b7930069ba5441a6cd1e7a59c21c546'/>
<id>urn:sha1:5195d8031b7930069ba5441a6cd1e7a59c21c546</id>
<content type='text'>
- Add relay config section (name, description, pubkey, contact, icon)
- Wire config to NIP-11 handler
- AuthRequired/RestrictedWrites now reflect actual auth config
- Add NIP-42 to supported_nips list
- Update example config
</content>
</entry>
<entry>
<title>refactor: migrate nostr dependency to code.northwest.io</title>
<updated>2026-02-16T20:04:20+00:00</updated>
<author>
<name>bndw</name>
<email>ben@bdw.to</email>
</author>
<published>2026-02-16T20:04:20+00:00</published>
<link rel='alternate' type='text/html' href='https://code.northwest.io/muxstr.git/commit/?id=02d6819ebfdc5b18d65c83c4077a040466960bac'/>
<id>urn:sha1:02d6819ebfdc5b18d65c83c4077a040466960bac</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>feat: add rate limiting to WebSocket connections</title>
<updated>2026-02-15T05:53:14+00:00</updated>
<author>
<name>bndw</name>
<email>ben@bdw.to</email>
</author>
<published>2026-02-15T05:53:14+00:00</published>
<link rel='alternate' type='text/html' href='https://code.northwest.io/muxstr.git/commit/?id=32ca0fba5108d0dc2c7415f36e55f031d5a0562e'/>
<id>urn:sha1:32ca0fba5108d0dc2c7415f36e55f031d5a0562e</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>refactor: use AuthStore interface and remove type assertions</title>
<updated>2026-02-14T22:54:30+00:00</updated>
<author>
<name>bndw</name>
<email>ben@bdw.to</email>
</author>
<published>2026-02-14T22:54:30+00:00</published>
<link rel='alternate' type='text/html' href='https://code.northwest.io/muxstr.git/commit/?id=4dbd96ec697196d43ad41eca4fd43d53da46a081'/>
<id>urn:sha1:4dbd96ec697196d43ad41eca4fd43d53da46a081</id>
<content type='text'>
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
</content>
</entry>
<entry>
<title>feat: implement NIP-42 WebSocket authentication</title>
<updated>2026-02-14T22:33:01+00:00</updated>
<author>
<name>bndw</name>
<email>ben@bdw.to</email>
</author>
<published>2026-02-14T22:33:01+00:00</published>
<link rel='alternate' type='text/html' href='https://code.northwest.io/muxstr.git/commit/?id=212154fc29e3631d13cf7af9a0a3046c9683173b'/>
<id>urn:sha1:212154fc29e3631d13cf7af9a0a3046c9683173b</id>
<content type='text'>
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", "&lt;challenge&gt;"]
3. Client signs kind 22242 event with challenge tag
4. Client sends: ["AUTH", &lt;signed-event&gt;]
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!
</content>
</entry>
<entry>
<title>feat: add storage stats and average latency metrics</title>
<updated>2026-02-14T20:53:50+00:00</updated>
<author>
<name>bndw</name>
<email>ben@bdw.to</email>
</author>
<published>2026-02-14T20:53:50+00:00</published>
<link rel='alternate' type='text/html' href='https://code.northwest.io/muxstr.git/commit/?id=75fb3a583cf4e8a7ca34bedb3322d309bb170ed4'/>
<id>urn:sha1:75fb3a583cf4e8a7ca34bedb3322d309bb170ed4</id>
<content type='text'>
Track and display storage and performance metrics that were previously
showing zeros.

Storage metrics:
- Add GetStats() method to storage returning event count and DB size
- Store database file path for size calculation
- Update metrics every 30 seconds via goroutine in main.go
- Display event count and DB size (MB) in dashboard

Performance metrics:
- Calculate average latency from histogram sum/count metrics
- Display as milliseconds in dashboard
- Formula: (duration_sum / duration_count) * 1000

Missing metrics (deferred):
- Connections: requires connection lifecycle tracking (gRPC + WebSocket)
- Deletions: count would need API change to ProcessDeletion

Dashboard now shows accurate storage stats and request latency!
</content>
</entry>
<entry>
<title>feat: track WebSocket subscriptions in metrics</title>
<updated>2026-02-14T20:49:08+00:00</updated>
<author>
<name>bndw</name>
<email>ben@bdw.to</email>
</author>
<published>2026-02-14T20:49:08+00:00</published>
<link rel='alternate' type='text/html' href='https://code.northwest.io/muxstr.git/commit/?id=40df56985402a31695a9a3bb13319bd2a3276305'/>
<id>urn:sha1:40df56985402a31695a9a3bb13319bd2a3276305</id>
<content type='text'>
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).
</content>
</entry>
<entry>
<title>fix: properly chain gRPC interceptors and fix dashboard uptime</title>
<updated>2026-02-14T20:29:01+00:00</updated>
<author>
<name>bndw</name>
<email>ben@bdw.to</email>
</author>
<published>2026-02-14T20:29:01+00:00</published>
<link rel='alternate' type='text/html' href='https://code.northwest.io/muxstr.git/commit/?id=c33395cee66770c4d88a47537c0ed821f6585f62'/>
<id>urn:sha1:c33395cee66770c4d88a47537c0ed821f6585f62</id>
<content type='text'>
Two critical fixes for metrics:

1. Fix interceptor chaining
   - Changed from mixed grpc.UnaryInterceptor/ChainUnaryInterceptor to
     proper chaining with grpc.ChainUnaryInterceptor
   - Metrics interceptor now runs first (as intended)
   - All interceptors properly chained in order: metrics → auth → ratelimit
   - This fixes metrics not being recorded for any requests

2. Fix dashboard uptime calculation
   - Changed from page load time to process_start_time_seconds metric
   - Uptime now persists correctly across page refreshes
   - Uses Prometheus standard process_start_time_seconds gauge

Before: Metrics showed 0 for all requests, uptime reset on refresh
After: Metrics properly record all gRPC requests, uptime shows actual relay uptime
</content>
</entry>
<entry>
<title>refactor: serve metrics on main HTTP port instead of separate port</title>
<updated>2026-02-14T20:19:29+00:00</updated>
<author>
<name>bndw</name>
<email>ben@bdw.to</email>
</author>
<published>2026-02-14T20:19:29+00:00</published>
<link rel='alternate' type='text/html' href='https://code.northwest.io/muxstr.git/commit/?id=eb666af39feed4be9c8c1354cf52d0ea38ab5d36'/>
<id>urn:sha1:eb666af39feed4be9c8c1354cf52d0ea38ab5d36</id>
<content type='text'>
Move metrics dashboard and Prometheus endpoint to the main HTTP server
for simplified deployment and single ingress configuration.

Changes:
- Add PrometheusHandler() and DashboardHandler() methods to Metrics
- Serve /dashboard on main HTTP port (was root on separate port)
- Serve /metrics on main HTTP port (was /metrics on separate port)
- Remove separate metrics server goroutine
- Update logging to show metrics paths on main HTTP port

Benefits:
- Single port/ingress needed for all HTTP traffic
- Simpler reverse proxy configuration
- Dashboard accessible alongside main relay endpoints

Endpoints on port 8080:
- / - WebSocket/index
- /nostr.v1.NostrRelay/* - Connect (gRPC-Web)
- /dashboard - Metrics dashboard (HTML)
- /metrics - Prometheus metrics (text)
</content>
</entry>
</feed>
