Add simplified NIP-46 bunker page with click-to-copy QR codes (v0.41.0)
Some checks failed
Go / build-and-release (push) Has been cancelled
Some checks failed
Go / build-and-release (push) Has been cancelled
- Add BunkerView with two QR codes: client (bunker://) and signer (nostr+connect://) - Add click-to-copy functionality on QR codes with visual "Copied!" feedback - Add CAT requirement warning (only shows when ACL mode is active) - Remove WireGuard dependencies from bunker page - Add /api/bunker/info public endpoint for relay URL, ACL mode, CAT status - Add Cashu token verification for WebSocket connections - Add kind permission checking for Cashu token scopes - Add cashuToken field to Listener for connection-level token tracking Files modified: - app/handle-bunker.go: New bunker info endpoint (without WireGuard) - app/handle-event.go: Add Cashu token kind permission check - app/handle-websocket.go: Extract and verify Cashu token on WS upgrade - app/listener.go: Add cashuToken field - app/server.go: Register bunker info endpoint - app/web/src/BunkerView.svelte: Complete rewrite with QR codes - app/web/src/api.js: Add getBunkerInfo() function - pkg/version/version: Bump to v0.41.0 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
||||
"lol.mleku.dev/log"
|
||||
"git.mleku.dev/mleku/nostr/encoders/envelopes/authenvelope"
|
||||
"git.mleku.dev/mleku/nostr/encoders/hex"
|
||||
"next.orly.dev/pkg/cashu/token"
|
||||
"next.orly.dev/pkg/protocol/publish"
|
||||
"git.mleku.dev/mleku/nostr/utils/units"
|
||||
)
|
||||
@@ -55,6 +56,12 @@ func (s *Server) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
whitelist:
|
||||
// Extract and verify Cashu access token if verifier is configured
|
||||
var cashuToken *token.Token
|
||||
if s.CashuVerifier != nil {
|
||||
cashuToken = s.extractWebSocketToken(r, remote)
|
||||
}
|
||||
|
||||
// Create an independent context for this connection
|
||||
// This context will be cancelled when the connection closes or server shuts down
|
||||
ctx, cancel := context.WithCancel(s.Ctx)
|
||||
@@ -99,6 +106,7 @@ whitelist:
|
||||
conn: conn,
|
||||
remote: remote,
|
||||
req: r,
|
||||
cashuToken: cashuToken, // Verified Cashu access token (nil if none provided)
|
||||
startTime: time.Now(),
|
||||
writeChan: make(chan publish.WriteRequest, 100), // Buffered channel for writes
|
||||
writeDone: make(chan struct{}),
|
||||
@@ -291,3 +299,54 @@ func (s *Server) Pinger(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// extractWebSocketToken extracts and verifies a Cashu access token from a WebSocket upgrade request.
|
||||
// Checks query param first (for browser WebSocket clients), then headers.
|
||||
// Returns nil if no token is provided or if token verification fails.
|
||||
func (s *Server) extractWebSocketToken(r *http.Request, remote string) *token.Token {
|
||||
// Try query param first (WebSocket clients often can't set custom headers)
|
||||
tokenStr := r.URL.Query().Get("token")
|
||||
|
||||
// Try X-Cashu-Token header
|
||||
if tokenStr == "" {
|
||||
tokenStr = r.Header.Get("X-Cashu-Token")
|
||||
}
|
||||
|
||||
// Try Authorization: Cashu scheme
|
||||
if tokenStr == "" {
|
||||
auth := r.Header.Get("Authorization")
|
||||
if strings.HasPrefix(auth, "Cashu ") {
|
||||
tokenStr = strings.TrimPrefix(auth, "Cashu ")
|
||||
}
|
||||
}
|
||||
|
||||
// No token provided - this is fine, connection proceeds without token
|
||||
if tokenStr == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse the token
|
||||
tok, err := token.Parse(tokenStr)
|
||||
if err != nil {
|
||||
log.W.F("ws %s: invalid Cashu token format: %v", remote, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verify token - accept both "relay" and "nip46" scopes for WebSocket connections
|
||||
// NIP-46 connections are also WebSocket-based
|
||||
ctx := context.Background()
|
||||
if err := s.CashuVerifier.Verify(ctx, tok, remote); err != nil {
|
||||
log.W.F("ws %s: Cashu token verification failed: %v", remote, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check scope - allow "relay" or "nip46"
|
||||
if tok.Scope != token.ScopeRelay && tok.Scope != token.ScopeNIP46 {
|
||||
log.W.F("ws %s: Cashu token has invalid scope %q for WebSocket", remote, tok.Scope)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.D.F("ws %s: verified Cashu token with scope %q, expires %v",
|
||||
remote, tok.Scope, tok.ExpiresAt())
|
||||
return tok
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user