nip43.go raw

   1  package neo4j
   2  
   3  import (
   4  	"encoding/json"
   5  	"fmt"
   6  	"time"
   7  
   8  	"next.orly.dev/pkg/database"
   9  	"next.orly.dev/pkg/nostr/encoders/hex"
  10  )
  11  
  12  // NIP-43 Invite-based ACL methods
  13  // Simplified implementation using marker-based storage via Badger
  14  // For production, these could use Neo4j nodes with relationships
  15  
  16  // AddNIP43Member adds a member using an invite code
  17  func (n *N) AddNIP43Member(pubkey []byte, inviteCode string) error {
  18  	key := "nip43_" + hex.Enc(pubkey)
  19  
  20  	member := database.NIP43Membership{
  21  		InviteCode: inviteCode,
  22  		AddedAt:    time.Now(),
  23  	}
  24  	copy(member.Pubkey[:], pubkey)
  25  
  26  	data, err := json.Marshal(member)
  27  	if err != nil {
  28  		return fmt.Errorf("failed to marshal membership: %w", err)
  29  	}
  30  
  31  	// Also add to members list
  32  	if err := n.addToMembersList(pubkey); err != nil {
  33  		return err
  34  	}
  35  
  36  	return n.SetMarker(key, data)
  37  }
  38  
  39  // RemoveNIP43Member removes a member
  40  func (n *N) RemoveNIP43Member(pubkey []byte) error {
  41  	key := "nip43_" + hex.Enc(pubkey)
  42  
  43  	// Remove from members list
  44  	if err := n.removeFromMembersList(pubkey); err != nil {
  45  		return err
  46  	}
  47  
  48  	return n.DeleteMarker(key)
  49  }
  50  
  51  // IsNIP43Member checks if a pubkey is a member
  52  func (n *N) IsNIP43Member(pubkey []byte) (isMember bool, err error) {
  53  	_, err = n.GetNIP43Membership(pubkey)
  54  	return err == nil, nil
  55  }
  56  
  57  // GetNIP43Membership retrieves membership information
  58  func (n *N) GetNIP43Membership(pubkey []byte) (*database.NIP43Membership, error) {
  59  	key := "nip43_" + hex.Enc(pubkey)
  60  
  61  	data, err := n.GetMarker(key)
  62  	if err != nil {
  63  		return nil, err
  64  	}
  65  
  66  	var member database.NIP43Membership
  67  	if err := json.Unmarshal(data, &member); err != nil {
  68  		return nil, fmt.Errorf("failed to unmarshal membership: %w", err)
  69  	}
  70  
  71  	return &member, nil
  72  }
  73  
  74  // GetAllNIP43Members retrieves all member pubkeys
  75  func (n *N) GetAllNIP43Members() ([][]byte, error) {
  76  	data, err := n.GetMarker("nip43_members_list")
  77  	if err != nil {
  78  		return nil, nil // No members = empty list
  79  	}
  80  
  81  	var members []string
  82  	if err := json.Unmarshal(data, &members); err != nil {
  83  		return nil, fmt.Errorf("failed to unmarshal members list: %w", err)
  84  	}
  85  
  86  	result := make([][]byte, 0, len(members))
  87  	for _, hexPubkey := range members {
  88  		pubkey, err := hex.Dec(hexPubkey)
  89  		if err != nil {
  90  			continue
  91  		}
  92  		result = append(result, pubkey)
  93  	}
  94  
  95  	return result, nil
  96  }
  97  
  98  // StoreInviteCode stores an invite code with expiration
  99  func (n *N) StoreInviteCode(code string, expiresAt time.Time) error {
 100  	key := "invite_" + code
 101  
 102  	inviteData := map[string]interface{}{
 103  		"code":      code,
 104  		"expiresAt": expiresAt,
 105  	}
 106  
 107  	data, err := json.Marshal(inviteData)
 108  	if err != nil {
 109  		return fmt.Errorf("failed to marshal invite: %w", err)
 110  	}
 111  
 112  	return n.SetMarker(key, data)
 113  }
 114  
 115  // ValidateInviteCode checks if an invite code is valid
 116  func (n *N) ValidateInviteCode(code string) (valid bool, err error) {
 117  	key := "invite_" + code
 118  
 119  	data, err := n.GetMarker(key)
 120  	if err != nil {
 121  		return false, nil // Code doesn't exist
 122  	}
 123  
 124  	var inviteData map[string]interface{}
 125  	if err := json.Unmarshal(data, &inviteData); err != nil {
 126  		return false, fmt.Errorf("failed to unmarshal invite: %w", err)
 127  	}
 128  
 129  	// Check expiration
 130  	if expiresStr, ok := inviteData["expiresAt"].(string); ok {
 131  		expiresAt, err := time.Parse(time.RFC3339, expiresStr)
 132  		if err == nil && time.Now().After(expiresAt) {
 133  			return false, nil // Expired
 134  		}
 135  	}
 136  
 137  	return true, nil
 138  }
 139  
 140  // DeleteInviteCode removes an invite code
 141  func (n *N) DeleteInviteCode(code string) error {
 142  	key := "invite_" + code
 143  	return n.DeleteMarker(key)
 144  }
 145  
 146  // PublishNIP43MembershipEvent publishes a membership event
 147  func (n *N) PublishNIP43MembershipEvent(kind int, pubkey []byte) error {
 148  	// This would require publishing an actual Nostr event
 149  	// For now, just log it
 150  	n.Logger.Infof("would publish NIP-43 event kind %d for %s", kind, hex.Enc(pubkey))
 151  	return nil
 152  }
 153  
 154  // addToMembersList adds a pubkey to the members list
 155  func (n *N) addToMembersList(pubkey []byte) error {
 156  	data, err := n.GetMarker("nip43_members_list")
 157  
 158  	var members []string
 159  	if err == nil {
 160  		if err := json.Unmarshal(data, &members); err != nil {
 161  			return fmt.Errorf("failed to unmarshal members list: %w", err)
 162  		}
 163  	}
 164  
 165  	hexPubkey := hex.Enc(pubkey)
 166  
 167  	// Check if already in list
 168  	for _, member := range members {
 169  		if member == hexPubkey {
 170  			return nil // Already in list
 171  		}
 172  	}
 173  
 174  	members = append(members, hexPubkey)
 175  
 176  	data, err = json.Marshal(members)
 177  	if err != nil {
 178  		return fmt.Errorf("failed to marshal members list: %w", err)
 179  	}
 180  
 181  	return n.SetMarker("nip43_members_list", data)
 182  }
 183  
 184  // removeFromMembersList removes a pubkey from the members list
 185  func (n *N) removeFromMembersList(pubkey []byte) error {
 186  	data, err := n.GetMarker("nip43_members_list")
 187  	if err != nil {
 188  		return nil // No list = nothing to remove
 189  	}
 190  
 191  	var members []string
 192  	if err := json.Unmarshal(data, &members); err != nil {
 193  		return fmt.Errorf("failed to unmarshal members list: %w", err)
 194  	}
 195  
 196  	hexPubkey := hex.Enc(pubkey)
 197  
 198  	// Filter out the pubkey
 199  	filtered := make([]string, 0, len(members))
 200  	for _, member := range members {
 201  		if member != hexPubkey {
 202  			filtered = append(filtered, member)
 203  		}
 204  	}
 205  
 206  	data, err = json.Marshal(filtered)
 207  	if err != nil {
 208  		return fmt.Errorf("failed to marshal members list: %w", err)
 209  	}
 210  
 211  	return n.SetMarker("nip43_members_list", data)
 212  }
 213