auth_test.go raw

   1  package relay
   2  
   3  import (
   4  	"context"
   5  	"errors"
   6  	"io"
   7  	"sync"
   8  	"testing"
   9  	"time"
  10  
  11  	"git.smesh.lol/nostrino/pkg/nostr"
  12  )
  13  
  14  type fakeTransport struct {
  15  	in     chan []byte
  16  	out    chan []byte
  17  	closed chan struct{}
  18  	once   sync.Once
  19  }
  20  
  21  func newFakeTransport() *fakeTransport {
  22  	return &fakeTransport{
  23  		in:     make(chan []byte, 8),
  24  		out:    make(chan []byte, 8),
  25  		closed: make(chan struct{}),
  26  	}
  27  }
  28  
  29  func (f *fakeTransport) WriteText(msg []byte) error {
  30  	cp := append([]byte(nil), msg...)
  31  	select {
  32  	case f.out <- cp:
  33  		return nil
  34  	case <-f.closed:
  35  		return errors.New("fake: closed")
  36  	}
  37  }
  38  
  39  func (f *fakeTransport) ReadMessage() ([]byte, error) {
  40  	select {
  41  	case msg := <-f.in:
  42  		return msg, nil
  43  	case <-f.closed:
  44  		return nil, io.EOF
  45  	}
  46  }
  47  
  48  func (f *fakeTransport) Close() error {
  49  	f.once.Do(func() { close(f.closed) })
  50  	return nil
  51  }
  52  
  53  func recv(t *testing.T, f *fakeTransport) [][]byte {
  54  	t.Helper()
  55  	select {
  56  	case msg := <-f.out:
  57  		parts, err := nostr.SplitArray(msg)
  58  		if err != nil {
  59  			t.Fatalf("out: %v", err)
  60  		}
  61  		return parts
  62  	case <-time.After(2 * time.Second):
  63  		t.Fatal("timed out waiting for client write")
  64  		return nil
  65  	}
  66  }
  67  
  68  func TestNIP42Auth(t *testing.T) {
  69  	f := newFakeTransport()
  70  	var key [32]byte
  71  	key[31] = 3
  72  	c, err := New(f, key, Options{RelayURL: "ws://relay.test"})
  73  	if err != nil {
  74  		t.Fatal(err)
  75  	}
  76  	defer c.Close()
  77  
  78  	f.in <- []byte(`["AUTH","challenge-xyz"]`)
  79  	parts := recv(t, f)
  80  	if len(parts) != 2 {
  81  		t.Fatalf("auth message parts %d", len(parts))
  82  	}
  83  	typ, _ := nostr.ParseJSONString(parts[0])
  84  	if typ != "AUTH" {
  85  		t.Fatalf("type %q", typ)
  86  	}
  87  	ev, err := nostr.ParseEvent(parts[1])
  88  	if err != nil {
  89  		t.Fatal(err)
  90  	}
  91  	if err := ev.Verify(); err != nil {
  92  		t.Fatal(err)
  93  	}
  94  	if ev.Kind != 22242 {
  95  		t.Fatalf("kind %d", ev.Kind)
  96  	}
  97  	if relay, _ := ev.Tags.First("relay"); relay != "ws://relay.test" {
  98  		t.Fatalf("relay tag %q", relay)
  99  	}
 100  	if challenge, _ := ev.Tags.First("challenge"); challenge != "challenge-xyz" {
 101  		t.Fatalf("challenge tag %q", challenge)
 102  	}
 103  
 104  	// A publish waits for the auth result, then writes an EVENT and waits for
 105  	// its own OK.
 106  	pub := make(chan error, 1)
 107  	go func() {
 108  		pub <- c.Publish(context.Background(), &nostr.Event{Kind: 1, Content: "hello"})
 109  	}()
 110  	select {
 111  	case parts := <-f.out:
 112  		t.Fatalf("published before auth completed: %s", string(parts[0]))
 113  	case <-time.After(100 * time.Millisecond):
 114  	}
 115  	id := nostr.Hex(ev.ID[:])
 116  	f.in <- []byte(`["OK","` + id + `",true,""]`)
 117  	parts = recv(t, f)
 118  	typ, _ = nostr.ParseJSONString(parts[0])
 119  	if typ != "EVENT" {
 120  		t.Fatalf("expected EVENT, got %q", typ)
 121  	}
 122  	event, err := nostr.ParseEvent(parts[1])
 123  	if err != nil {
 124  		t.Fatal(err)
 125  	}
 126  	f.in <- []byte(`["OK","` + nostr.Hex(event.ID[:]) + `",true,""]`)
 127  	select {
 128  	case err := <-pub:
 129  		if err != nil {
 130  			t.Fatalf("publish: %v", err)
 131  		}
 132  	case <-time.After(2 * time.Second):
 133  		t.Fatal("publish did not return")
 134  	}
 135  }
 136  
 137  func TestNIP42AuthRejected(t *testing.T) {
 138  	f := newFakeTransport()
 139  	var key [32]byte
 140  	key[31] = 4
 141  	c, err := New(f, key, Options{RelayURL: "ws://relay.test"})
 142  	if err != nil {
 143  		t.Fatal(err)
 144  	}
 145  	defer c.Close()
 146  	f.in <- []byte(`["AUTH","challenge"]`)
 147  	parts := recv(t, f)
 148  	ev, err := nostr.ParseEvent(parts[1])
 149  	if err != nil {
 150  		t.Fatal(err)
 151  	}
 152  	f.in <- []byte(`["OK","` + nostr.Hex(ev.ID[:]) + `",false,"auth-required: nope"]`)
 153  	time.Sleep(50 * time.Millisecond)
 154  	if err := c.Send(&nostr.Event{Kind: 1, Content: "x"}); err == nil {
 155  		t.Fatal("send succeeded after rejected auth")
 156  	}
 157  }
 158  
 159  func TestAuthEvent(t *testing.T) {
 160  	var key [32]byte
 161  	key[31] = 5
 162  	ev := AuthEvent("wss://relay", "c", key)
 163  	if ev == nil {
 164  		t.Fatal("no event")
 165  	}
 166  	if err := ev.Verify(); err != nil {
 167  		t.Fatal(err)
 168  	}
 169  	if v, _ := ev.Tags.First("relay"); v != "wss://relay" {
 170  		t.Fatal("relay tag")
 171  	}
 172  }
 173