diff --git a/app/handle-cashu.go b/app/handle-cashu.go index b911366..689648f 100644 --- a/app/handle-cashu.go +++ b/app/handle-cashu.go @@ -23,15 +23,27 @@ type CashuMintRequest struct { } // CashuMintResponse is the response body for token issuance. +// Field names match NIP-XX Cashu Access Tokens spec. type CashuMintResponse struct { BlindedSignature string `json:"blinded_signature"` // Hex-encoded blinded signature C_ KeysetID string `json:"keyset_id"` // Keyset ID used Expiry int64 `json:"expiry"` // Token expiration timestamp - MintPubkey string `json:"mint_pubkey"` // Hex-encoded mint public key + MintPubkey string `json:"pubkey"` // Hex-encoded mint public key (spec: "pubkey") } // handleCashuMint handles POST /cashu/mint - issues a new token. func (s *Server) handleCashuMint(w http.ResponseWriter, r *http.Request) { + // CORS headers for browser-based CAT token requests + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization") + + // Handle preflight + if r.Method == http.MethodOptions { + w.WriteHeader(http.StatusNoContent) + return + } + // Check if Cashu is enabled if s.CashuIssuer == nil { log.W.F("Cashu mint request but issuer not initialized") @@ -107,6 +119,17 @@ func (s *Server) handleCashuMint(w http.ResponseWriter, r *http.Request) { // handleCashuKeysets handles GET /cashu/keysets - returns available keysets. func (s *Server) handleCashuKeysets(w http.ResponseWriter, r *http.Request) { + // CORS headers for browser-based CAT support + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Accept") + + // Handle preflight + if r.Method == http.MethodOptions { + w.WriteHeader(http.StatusNoContent) + return + } + if s.CashuIssuer == nil { http.Error(w, "Cashu tokens not enabled", http.StatusNotImplemented) return diff --git a/app/handle-event.go b/app/handle-event.go index f728c85..e5d92fa 100644 --- a/app/handle-event.go +++ b/app/handle-event.go @@ -146,8 +146,9 @@ func (l *Listener) HandleEvent(msg []byte) (err error) { // Require Cashu token for NIP-46 events when Cashu is enabled and ACL is active const kindNIP46 = 24133 if env.E.Kind == kindNIP46 && l.CashuVerifier != nil && l.Config.ACLMode != "none" { + log.D.F("HandleEvent: NIP-46 event from %s, cashuToken=%v, ACLMode=%s", l.remote, l.cashuToken != nil, l.Config.ACLMode) if l.cashuToken == nil { - log.W.F("HandleEvent: rejecting NIP-46 event - Cashu access token required") + log.W.F("HandleEvent: rejecting NIP-46 event from %s - Cashu access token required (connection has no token)", l.remote) if err = Ok.Error(l, env, "restricted: NIP-46 requires Cashu access token"); chk.E(err) { return } diff --git a/app/handle-websocket.go b/app/handle-websocket.go index e7a9367..9f4713a 100644 --- a/app/handle-websocket.go +++ b/app/handle-websocket.go @@ -309,10 +309,12 @@ func (s *Server) Pinger( 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") + log.D.F("ws %s: CAT extraction - query param token: %v", remote, tokenStr != "") // Try X-Cashu-Token header if tokenStr == "" { tokenStr = r.Header.Get("X-Cashu-Token") + log.D.F("ws %s: CAT extraction - X-Cashu-Token header: %v", remote, tokenStr != "") } // Try Authorization: Cashu scheme @@ -321,12 +323,15 @@ func (s *Server) extractWebSocketToken(r *http.Request, remote string) *token.To if strings.HasPrefix(auth, "Cashu ") { tokenStr = strings.TrimPrefix(auth, "Cashu ") } + log.D.F("ws %s: CAT extraction - Authorization header: %v", remote, tokenStr != "") } // No token provided - this is fine, connection proceeds without token if tokenStr == "" { + log.D.F("ws %s: CAT extraction - no token found", remote) return nil } + log.D.F("ws %s: CAT extraction - found token (len=%d)", remote, len(tokenStr)) // Parse the token tok, err := token.Parse(tokenStr) diff --git a/pkg/cashu/issuer/issuer.go b/pkg/cashu/issuer/issuer.go index 7b18eec..7b7ae8f 100644 --- a/pkg/cashu/issuer/issuer.go +++ b/pkg/cashu/issuer/issuer.go @@ -3,6 +3,7 @@ package issuer import ( "context" + "encoding/hex" "errors" "fmt" "time" @@ -222,22 +223,28 @@ func (i *Issuer) GetActiveKeysetID() string { // MintInfo contains public information about the mint. type MintInfo struct { - Name string `json:"name,omitempty"` - Version string `json:"version"` - TokenTTL int64 `json:"token_ttl"` - MaxKinds int `json:"max_kinds,omitempty"` - MaxKindRanges int `json:"max_kind_ranges,omitempty"` + Name string `json:"name,omitempty"` + Version string `json:"version"` + Pubkey string `json:"pubkey"` + TokenTTL int64 `json:"token_ttl"` + MaxKinds int `json:"max_kinds,omitempty"` + MaxKindRanges int `json:"max_kind_ranges,omitempty"` SupportedScopes []string `json:"supported_scopes,omitempty"` } // GetMintInfo returns public information about the issuer. func (i *Issuer) GetMintInfo(name string) MintInfo { + var pubkeyHex string + if ks := i.keysets.GetSigningKeyset(); ks != nil { + pubkeyHex = hex.EncodeToString(ks.SerializePublicKey()) + } return MintInfo{ - Name: name, - Version: "NIP-XX/1", - TokenTTL: int64(i.config.DefaultTTL.Seconds()), - MaxKinds: i.config.MaxKinds, - MaxKindRanges: i.config.MaxKindRanges, + Name: name, + Version: "NIP-XX/1", + Pubkey: pubkeyHex, + TokenTTL: int64(i.config.DefaultTTL.Seconds()), + MaxKinds: i.config.MaxKinds, + MaxKindRanges: i.config.MaxKindRanges, SupportedScopes: i.config.AllowedScopes, } } diff --git a/pkg/version/version b/pkg/version/version index 2474ad1..6295103 100644 --- a/pkg/version/version +++ b/pkg/version/version @@ -1 +1 @@ -v0.50.1 +v0.51.0 \ No newline at end of file