- Improved Cashu token handling and validation - Better error messages for token verification - Version bump to v0.51.0 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,15 +23,27 @@ type CashuMintRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CashuMintResponse is the response body for token issuance.
|
// CashuMintResponse is the response body for token issuance.
|
||||||
|
// Field names match NIP-XX Cashu Access Tokens spec.
|
||||||
type CashuMintResponse struct {
|
type CashuMintResponse struct {
|
||||||
BlindedSignature string `json:"blinded_signature"` // Hex-encoded blinded signature C_
|
BlindedSignature string `json:"blinded_signature"` // Hex-encoded blinded signature C_
|
||||||
KeysetID string `json:"keyset_id"` // Keyset ID used
|
KeysetID string `json:"keyset_id"` // Keyset ID used
|
||||||
Expiry int64 `json:"expiry"` // Token expiration timestamp
|
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.
|
// handleCashuMint handles POST /cashu/mint - issues a new token.
|
||||||
func (s *Server) handleCashuMint(w http.ResponseWriter, r *http.Request) {
|
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
|
// Check if Cashu is enabled
|
||||||
if s.CashuIssuer == nil {
|
if s.CashuIssuer == nil {
|
||||||
log.W.F("Cashu mint request but issuer not initialized")
|
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.
|
// handleCashuKeysets handles GET /cashu/keysets - returns available keysets.
|
||||||
func (s *Server) handleCashuKeysets(w http.ResponseWriter, r *http.Request) {
|
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 {
|
if s.CashuIssuer == nil {
|
||||||
http.Error(w, "Cashu tokens not enabled", http.StatusNotImplemented)
|
http.Error(w, "Cashu tokens not enabled", http.StatusNotImplemented)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -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
|
// Require Cashu token for NIP-46 events when Cashu is enabled and ACL is active
|
||||||
const kindNIP46 = 24133
|
const kindNIP46 = 24133
|
||||||
if env.E.Kind == kindNIP46 && l.CashuVerifier != nil && l.Config.ACLMode != "none" {
|
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 {
|
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) {
|
if err = Ok.Error(l, env, "restricted: NIP-46 requires Cashu access token"); chk.E(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -309,10 +309,12 @@ func (s *Server) Pinger(
|
|||||||
func (s *Server) extractWebSocketToken(r *http.Request, remote string) *token.Token {
|
func (s *Server) extractWebSocketToken(r *http.Request, remote string) *token.Token {
|
||||||
// Try query param first (WebSocket clients often can't set custom headers)
|
// Try query param first (WebSocket clients often can't set custom headers)
|
||||||
tokenStr := r.URL.Query().Get("token")
|
tokenStr := r.URL.Query().Get("token")
|
||||||
|
log.D.F("ws %s: CAT extraction - query param token: %v", remote, tokenStr != "")
|
||||||
|
|
||||||
// Try X-Cashu-Token header
|
// Try X-Cashu-Token header
|
||||||
if tokenStr == "" {
|
if tokenStr == "" {
|
||||||
tokenStr = r.Header.Get("X-Cashu-Token")
|
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
|
// Try Authorization: Cashu scheme
|
||||||
@@ -321,12 +323,15 @@ func (s *Server) extractWebSocketToken(r *http.Request, remote string) *token.To
|
|||||||
if strings.HasPrefix(auth, "Cashu ") {
|
if strings.HasPrefix(auth, "Cashu ") {
|
||||||
tokenStr = strings.TrimPrefix(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
|
// No token provided - this is fine, connection proceeds without token
|
||||||
if tokenStr == "" {
|
if tokenStr == "" {
|
||||||
|
log.D.F("ws %s: CAT extraction - no token found", remote)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
log.D.F("ws %s: CAT extraction - found token (len=%d)", remote, len(tokenStr))
|
||||||
|
|
||||||
// Parse the token
|
// Parse the token
|
||||||
tok, err := token.Parse(tokenStr)
|
tok, err := token.Parse(tokenStr)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package issuer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
@@ -224,6 +225,7 @@ func (i *Issuer) GetActiveKeysetID() string {
|
|||||||
type MintInfo struct {
|
type MintInfo struct {
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
|
Pubkey string `json:"pubkey"`
|
||||||
TokenTTL int64 `json:"token_ttl"`
|
TokenTTL int64 `json:"token_ttl"`
|
||||||
MaxKinds int `json:"max_kinds,omitempty"`
|
MaxKinds int `json:"max_kinds,omitempty"`
|
||||||
MaxKindRanges int `json:"max_kind_ranges,omitempty"`
|
MaxKindRanges int `json:"max_kind_ranges,omitempty"`
|
||||||
@@ -232,9 +234,14 @@ type MintInfo struct {
|
|||||||
|
|
||||||
// GetMintInfo returns public information about the issuer.
|
// GetMintInfo returns public information about the issuer.
|
||||||
func (i *Issuer) GetMintInfo(name string) MintInfo {
|
func (i *Issuer) GetMintInfo(name string) MintInfo {
|
||||||
|
var pubkeyHex string
|
||||||
|
if ks := i.keysets.GetSigningKeyset(); ks != nil {
|
||||||
|
pubkeyHex = hex.EncodeToString(ks.SerializePublicKey())
|
||||||
|
}
|
||||||
return MintInfo{
|
return MintInfo{
|
||||||
Name: name,
|
Name: name,
|
||||||
Version: "NIP-XX/1",
|
Version: "NIP-XX/1",
|
||||||
|
Pubkey: pubkeyHex,
|
||||||
TokenTTL: int64(i.config.DefaultTTL.Seconds()),
|
TokenTTL: int64(i.config.DefaultTTL.Seconds()),
|
||||||
MaxKinds: i.config.MaxKinds,
|
MaxKinds: i.config.MaxKinds,
|
||||||
MaxKindRanges: i.config.MaxKindRanges,
|
MaxKindRanges: i.config.MaxKindRanges,
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
v0.50.1
|
v0.51.0
|
||||||
Reference in New Issue
Block a user