handle-nip43.go raw

   1  package app
   2  
   3  import (
   4  	"context"
   5  	"fmt"
   6  	"strings"
   7  	"time"
   8  
   9  	"next.orly.dev/pkg/lol/chk"
  10  	"next.orly.dev/pkg/lol/log"
  11  	"next.orly.dev/pkg/acl"
  12  	"next.orly.dev/pkg/nostr/encoders/envelopes/okenvelope"
  13  	"next.orly.dev/pkg/nostr/encoders/event"
  14  	"next.orly.dev/pkg/nostr/encoders/hex"
  15  	"next.orly.dev/pkg/protocol/nip43"
  16  )
  17  
  18  // HandleNIP43JoinRequest processes a kind 28934 join request
  19  func (l *Listener) HandleNIP43JoinRequest(ev *event.E) error {
  20  	log.D.F("handling NIP-43 join request from %s", hex.Enc(ev.Pubkey))
  21  
  22  	// Validate the join request
  23  	inviteCode, valid, reason := nip43.ValidateJoinRequest(ev)
  24  	if !valid {
  25  		log.W.F("invalid join request: %s", reason)
  26  		return l.sendOKResponse(ev.ID, false, fmt.Sprintf("restricted: %s", reason))
  27  	}
  28  
  29  	// Check if user is already a member
  30  	isMember, err := l.DB.IsNIP43Member(ev.Pubkey)
  31  	if chk.E(err) {
  32  		log.E.F("error checking membership: %v", err)
  33  		return l.sendOKResponse(ev.ID, false, "error: internal server error")
  34  	}
  35  
  36  	if isMember {
  37  		log.D.F("user %s is already a member", hex.Enc(ev.Pubkey))
  38  		return l.sendOKResponse(ev.ID, true, "duplicate: you are already a member of this relay")
  39  	}
  40  
  41  	// Validate the invite code
  42  	validCode, reason := l.Server.InviteManager.ValidateAndConsume(inviteCode, ev.Pubkey)
  43  
  44  	if !validCode {
  45  		log.W.F("invalid or expired invite code: %s - %s", inviteCode, reason)
  46  		return l.sendOKResponse(ev.ID, false, fmt.Sprintf("restricted: %s", reason))
  47  	}
  48  
  49  	// Add the member
  50  	if err = l.DB.AddNIP43Member(ev.Pubkey, inviteCode); chk.E(err) {
  51  		log.E.F("error adding member: %v", err)
  52  		return l.sendOKResponse(ev.ID, false, "error: failed to add member")
  53  	}
  54  
  55  	log.I.F("successfully added member %s via invite code", hex.Enc(ev.Pubkey))
  56  
  57  	// Publish kind 8000 "add member" event if configured
  58  	if l.Config.NIP43PublishEvents {
  59  		if err = l.publishAddUserEvent(ev.Pubkey); chk.E(err) {
  60  			log.W.F("failed to publish add user event: %v", err)
  61  		}
  62  	}
  63  
  64  	// Update membership list if configured
  65  	if l.Config.NIP43PublishMemberList {
  66  		if err = l.publishMembershipList(); chk.E(err) {
  67  			log.W.F("failed to publish membership list: %v", err)
  68  		}
  69  	}
  70  
  71  	relayURL := l.Config.RelayURL
  72  	if relayURL == "" {
  73  		relayURL = fmt.Sprintf("wss://%s:%d", l.Config.Listen, l.Config.Port)
  74  	}
  75  
  76  	return l.sendOKResponse(ev.ID, true, fmt.Sprintf("welcome to %s!", relayURL))
  77  }
  78  
  79  // HandleNIP43LeaveRequest processes a kind 28936 leave request
  80  func (l *Listener) HandleNIP43LeaveRequest(ev *event.E) error {
  81  	log.D.F("handling NIP-43 leave request from %s", hex.Enc(ev.Pubkey))
  82  
  83  	// Validate the leave request
  84  	valid, reason := nip43.ValidateLeaveRequest(ev)
  85  	if !valid {
  86  		log.W.F("invalid leave request: %s", reason)
  87  		return l.sendOKResponse(ev.ID, false, fmt.Sprintf("error: %s", reason))
  88  	}
  89  
  90  	// Check if user is a member
  91  	isMember, err := l.DB.IsNIP43Member(ev.Pubkey)
  92  	if chk.E(err) {
  93  		log.E.F("error checking membership: %v", err)
  94  		return l.sendOKResponse(ev.ID, false, "error: internal server error")
  95  	}
  96  
  97  	if !isMember {
  98  		log.D.F("user %s is not a member", hex.Enc(ev.Pubkey))
  99  		return l.sendOKResponse(ev.ID, true, "you are not a member of this relay")
 100  	}
 101  
 102  	// Remove the member
 103  	if err = l.DB.RemoveNIP43Member(ev.Pubkey); chk.E(err) {
 104  		log.E.F("error removing member: %v", err)
 105  		return l.sendOKResponse(ev.ID, false, "error: failed to remove member")
 106  	}
 107  
 108  	log.I.F("successfully removed member %s", hex.Enc(ev.Pubkey))
 109  
 110  	// Publish kind 8001 "remove member" event if configured
 111  	if l.Config.NIP43PublishEvents {
 112  		if err = l.publishRemoveUserEvent(ev.Pubkey); chk.E(err) {
 113  			log.W.F("failed to publish remove user event: %v", err)
 114  		}
 115  	}
 116  
 117  	// Update membership list if configured
 118  	if l.Config.NIP43PublishMemberList {
 119  		if err = l.publishMembershipList(); chk.E(err) {
 120  			log.W.F("failed to publish membership list: %v", err)
 121  		}
 122  	}
 123  
 124  	return l.sendOKResponse(ev.ID, true, "you have been removed from this relay")
 125  }
 126  
 127  // HandleNIP43InviteRequest processes a kind 28935 invite request (REQ subscription)
 128  func (s *Server) HandleNIP43InviteRequest(pubkey []byte) (*event.E, error) {
 129  	log.D.F("generating NIP-43 invite for pubkey %s", hex.Enc(pubkey))
 130  
 131  	// Check if requester has permission to request invites
 132  	// This could be based on ACL, admins, etc.
 133  	accessLevel := acl.Registry.GetAccessLevel(pubkey, "")
 134  	if accessLevel != "admin" && accessLevel != "owner" {
 135  		log.W.F("unauthorized invite request from %s (level: %s)", hex.Enc(pubkey), accessLevel)
 136  		return nil, fmt.Errorf("unauthorized: only admins can request invites")
 137  	}
 138  
 139  	// Generate a new invite code
 140  	code, err := s.InviteManager.GenerateCode()
 141  	if chk.E(err) {
 142  		return nil, err
 143  	}
 144  
 145  	// Get relay identity
 146  	relaySecret, err := s.db.GetOrCreateRelayIdentitySecret()
 147  	if chk.E(err) {
 148  		return nil, err
 149  	}
 150  
 151  	// Build the invite event
 152  	inviteEvent, err := nip43.BuildInviteEvent(relaySecret, code)
 153  	if chk.E(err) {
 154  		return nil, err
 155  	}
 156  
 157  	log.D.F("generated invite code for %s", hex.Enc(pubkey))
 158  	return inviteEvent, nil
 159  }
 160  
 161  // publishAddUserEvent publishes a kind 8000 add user event
 162  func (l *Listener) publishAddUserEvent(userPubkey []byte) error {
 163  	relaySecret, err := l.DB.GetOrCreateRelayIdentitySecret()
 164  	if chk.E(err) {
 165  		return err
 166  	}
 167  
 168  	ev, err := nip43.BuildAddUserEvent(relaySecret, userPubkey)
 169  	if chk.E(err) {
 170  		return err
 171  	}
 172  
 173  	// Save to database
 174  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 175  	defer cancel()
 176  	if _, err = l.DB.SaveEvent(ctx, ev); chk.E(err) {
 177  		return err
 178  	}
 179  
 180  	// Publish to subscribers
 181  	l.publishers.Deliver(ev)
 182  
 183  	log.I.F("published kind 8000 add user event for %s", hex.Enc(userPubkey))
 184  	return nil
 185  }
 186  
 187  // publishRemoveUserEvent publishes a kind 8001 remove user event
 188  func (l *Listener) publishRemoveUserEvent(userPubkey []byte) error {
 189  	relaySecret, err := l.DB.GetOrCreateRelayIdentitySecret()
 190  	if chk.E(err) {
 191  		return err
 192  	}
 193  
 194  	ev, err := nip43.BuildRemoveUserEvent(relaySecret, userPubkey)
 195  	if chk.E(err) {
 196  		return err
 197  	}
 198  
 199  	// Save to database
 200  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 201  	defer cancel()
 202  	if _, err = l.DB.SaveEvent(ctx, ev); chk.E(err) {
 203  		return err
 204  	}
 205  
 206  	// Publish to subscribers
 207  	l.publishers.Deliver(ev)
 208  
 209  	log.I.F("published kind 8001 remove user event for %s", hex.Enc(userPubkey))
 210  	return nil
 211  }
 212  
 213  // publishMembershipList publishes a kind 13534 membership list event
 214  func (l *Listener) publishMembershipList() error {
 215  	// Get all members
 216  	members, err := l.DB.GetAllNIP43Members()
 217  	if chk.E(err) {
 218  		return err
 219  	}
 220  
 221  	relaySecret, err := l.DB.GetOrCreateRelayIdentitySecret()
 222  	if chk.E(err) {
 223  		return err
 224  	}
 225  
 226  	ev, err := nip43.BuildMemberListEvent(relaySecret, members)
 227  	if chk.E(err) {
 228  		return err
 229  	}
 230  
 231  	// Save to database
 232  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 233  	defer cancel()
 234  	if _, err = l.DB.SaveEvent(ctx, ev); chk.E(err) {
 235  		return err
 236  	}
 237  
 238  	// Publish to subscribers
 239  	l.publishers.Deliver(ev)
 240  
 241  	log.I.F("published kind 13534 membership list event with %d members", len(members))
 242  	return nil
 243  }
 244  
 245  // sendOKResponse sends an OK envelope response
 246  func (l *Listener) sendOKResponse(eventID []byte, accepted bool, message string) error {
 247  	// Ensure message doesn't have "restricted: " prefix if already present
 248  	if accepted && strings.HasPrefix(message, "restricted: ") {
 249  		message = strings.TrimPrefix(message, "restricted: ")
 250  	}
 251  
 252  	env := okenvelope.NewFrom(eventID, accepted, []byte(message))
 253  	return env.Write(l)
 254  }
 255