Compare commits

..

6 Commits

6 changed files with 111 additions and 64 deletions

View File

@@ -122,16 +122,26 @@ func (l *Listener) HandleEvent(msg []byte) (err error) {
if _, _, err = l.SaveEvent(l.Ctx, env.E); chk.E(err) { if _, _, err = l.SaveEvent(l.Ctx, env.E); chk.E(err) {
return return
} }
// if a follow list was saved, reconfigure ACLs now that it is persisted
if env.E.Kind == kind.FollowList.K {
if err = acl.Registry.Configure(); chk.E(err) {
}
}
l.publishers.Deliver(env.E)
// Send a success response storing // Send a success response storing
if err = Ok.Ok(l, env, ""); chk.E(err) { if err = Ok.Ok(l, env, ""); chk.E(err) {
return return
} }
defer l.publishers.Deliver(env.E)
log.D.F("saved event %0x", env.E.ID) log.D.F("saved event %0x", env.E.ID)
var isNewFromAdmin bool
for _, admin := range l.Admins {
if utils.FastEqual(admin, env.E.Pubkey) {
isNewFromAdmin = true
break
}
}
if isNewFromAdmin {
// if a follow list was saved, reconfigure ACLs now that it is persisted
if env.E.Kind == kind.FollowList.K ||
env.E.Kind == kind.RelayListMetadata.K {
if err = acl.Registry.Configure(); chk.E(err) {
}
}
}
return return
} }

View File

@@ -33,32 +33,32 @@ func (s *Server) HandleRelayInfo(w http.ResponseWriter, r *http.Request) {
relayinfo.BasicProtocol, relayinfo.BasicProtocol,
// relayinfo.Authentication, // relayinfo.Authentication,
// relayinfo.EncryptedDirectMessage, // relayinfo.EncryptedDirectMessage,
// relayinfo.EventDeletion, relayinfo.EventDeletion,
relayinfo.RelayInformationDocument, relayinfo.RelayInformationDocument,
// relayinfo.GenericTagQueries, // relayinfo.GenericTagQueries,
// relayinfo.NostrMarketplace, // relayinfo.NostrMarketplace,
// relayinfo.EventTreatment, relayinfo.EventTreatment,
// relayinfo.CommandResults, // relayinfo.CommandResults,
// relayinfo.ParameterizedReplaceableEvents, relayinfo.ParameterizedReplaceableEvents,
// relayinfo.ExpirationTimestamp, // relayinfo.ExpirationTimestamp,
// relayinfo.ProtectedEvents, relayinfo.ProtectedEvents,
// relayinfo.RelayListMetadata, relayinfo.RelayListMetadata,
) )
if s.Config.ACLMode != "none" { if s.Config.ACLMode != "none" {
supportedNIPs = relayinfo.GetList( supportedNIPs = relayinfo.GetList(
relayinfo.BasicProtocol, relayinfo.BasicProtocol,
relayinfo.Authentication, relayinfo.Authentication,
// relayinfo.EncryptedDirectMessage, // relayinfo.EncryptedDirectMessage,
// relayinfo.EventDeletion, relayinfo.EventDeletion,
relayinfo.RelayInformationDocument, relayinfo.RelayInformationDocument,
// relayinfo.GenericTagQueries, // relayinfo.GenericTagQueries,
// relayinfo.NostrMarketplace, // relayinfo.NostrMarketplace,
// relayinfo.EventTreatment, relayinfo.EventTreatment,
// relayinfo.CommandResults, // relayinfo.CommandResults,
// relayinfo.ParameterizedReplaceableEvents, // relayinfo.ParameterizedReplaceableEvents,
// relayinfo.ExpirationTimestamp, // relayinfo.ExpirationTimestamp,
// relayinfo.ProtectedEvents, relayinfo.ProtectedEvents,
// relayinfo.RelayListMetadata, relayinfo.RelayListMetadata,
) )
} }
sort.Sort(supportedNIPs) sort.Sort(supportedNIPs)

View File

@@ -177,6 +177,7 @@ privCheck:
Id: string(env.Subscription), Id: string(env.Subscription),
Receiver: receiver, Receiver: receiver,
Filters: env.Filters, Filters: env.Filters,
AuthedPubkey: l.authedPubkey.Load(),
}, },
) )
} else { } else {

View File

@@ -8,17 +8,21 @@ import (
"encoders.orly/envelopes/eventenvelope" "encoders.orly/envelopes/eventenvelope"
"encoders.orly/event" "encoders.orly/event"
"encoders.orly/filter" "encoders.orly/filter"
"encoders.orly/hex"
"encoders.orly/kind"
"github.com/coder/websocket" "github.com/coder/websocket"
"interfaces.orly/publisher" "interfaces.orly/publisher"
"interfaces.orly/typer" "interfaces.orly/typer"
"lol.mleku.dev/chk" "lol.mleku.dev/chk"
"lol.mleku.dev/log" "lol.mleku.dev/log"
utils "utils.orly"
) )
const Type = "socketapi" const Type = "socketapi"
type Subscription struct { type Subscription struct {
remote string remote string
AuthedPubkey []byte
*filter.S *filter.S
} }
@@ -46,6 +50,9 @@ type W struct {
// associated with this WebSocket connection. It is used to determine which // associated with this WebSocket connection. It is used to determine which
// notifications or data should be received by the subscriber. // notifications or data should be received by the subscriber.
Filters *filter.S Filters *filter.S
// AuthedPubkey is the authenticated pubkey associated with the listener (if any).
AuthedPubkey []byte
} }
func (w *W) Type() (typeName string) { return Type } func (w *W) Type() (typeName string) { return Type }
@@ -56,7 +63,7 @@ func (w *W) Type() (typeName string) { return Type }
type P struct { type P struct {
c context.Context c context.Context
// Mx is the mutex for the Map. // Mx is the mutex for the Map.
Mx sync.Mutex Mx sync.RWMutex
// Map is the map of subscribers and subscriptions from the websocket api. // Map is the map of subscribers and subscriptions from the websocket api.
Map Map
} }
@@ -112,7 +119,7 @@ func (p *P) Receive(msg typer.T) {
defer p.Mx.Unlock() defer p.Mx.Unlock()
if subs, ok := p.Map[m.Conn]; !ok { if subs, ok := p.Map[m.Conn]; !ok {
subs = make(map[string]Subscription) subs = make(map[string]Subscription)
subs[m.Id] = Subscription{S: m.Filters, remote: m.remote} subs[m.Id] = Subscription{S: m.Filters, remote: m.remote, AuthedPubkey: m.AuthedPubkey}
p.Map[m.Conn] = subs p.Map[m.Conn] = subs
log.D.C( log.D.C(
func() string { func() string {
@@ -124,7 +131,7 @@ func (p *P) Receive(msg typer.T) {
}, },
) )
} else { } else {
subs[m.Id] = Subscription{S: m.Filters, remote: m.remote} subs[m.Id] = Subscription{S: m.Filters, remote: m.remote, AuthedPubkey: m.AuthedPubkey}
log.D.C( log.D.C(
func() string { func() string {
return fmt.Sprintf( return fmt.Sprintf(
@@ -150,55 +157,85 @@ func (p *P) Receive(msg typer.T) {
// for unauthenticated users when events are privileged. // for unauthenticated users when events are privileged.
func (p *P) Deliver(ev *event.E) { func (p *P) Deliver(ev *event.E) {
var err error var err error
p.Mx.Lock() // Snapshot the deliveries under read lock to avoid holding locks during I/O
defer p.Mx.Unlock() p.Mx.RLock()
type delivery struct {
w *websocket.Conn
id string
sub Subscription
}
var deliveries []delivery
for w, subs := range p.Map {
for id, subscriber := range subs {
if subscriber.Match(ev) {
deliveries = append(deliveries, delivery{w: w, id: id, sub: subscriber})
}
}
}
p.Mx.RUnlock()
log.D.C( log.D.C(
func() string { func() string {
return fmt.Sprintf( return fmt.Sprintf(
"delivering event %0x to websocket subscribers %d", ev.ID, "delivering event %0x to websocket subscribers %d", ev.ID,
len(p.Map), len(deliveries),
) )
}, },
) )
for w, subs := range p.Map { for _, d := range deliveries {
for id, subscriber := range subs { // If the event is privileged, enforce that the subscriber's authed pubkey matches
if !subscriber.Match(ev) { // either the event pubkey or appears in any 'p' tag of the event.
if kind.IsPrivileged(ev.Kind) && len(d.sub.AuthedPubkey) > 0 {
pk := d.sub.AuthedPubkey
allowed := false
// Direct author match
if utils.FastEqual(ev.Pubkey, pk) {
allowed = true
} else if ev.Tags != nil {
for _, pTag := range ev.Tags.GetAll([]byte("p")) {
// pTag.Value() returns []byte hex string; decode to bytes
dec, derr := hex.Dec(string(pTag.Value()))
if derr != nil {
continue continue
} }
// if p.Server.AuthRequired() { if utils.FastEqual(dec, pk) {
// if !auth.CheckPrivilege(w.AuthedPubkey(), ev) { allowed = true
// continue break
// } }
// } }
}
if !allowed {
// Skip delivery for this subscriber
continue
}
}
var res *eventenvelope.Result var res *eventenvelope.Result
if res, err = eventenvelope.NewResultWith(id, ev); chk.E(err) { if res, err = eventenvelope.NewResultWith(d.id, ev); chk.E(err) {
continue continue
} }
if err = w.Write( if err = d.w.Write(
p.c, websocket.MessageText, res.Marshal(nil), p.c, websocket.MessageText, res.Marshal(nil),
); chk.E(err) { ); chk.E(err) {
p.removeSubscriber(w) // On error, remove the subscriber connection safely
if err = w.CloseNow(); chk.E(err) { p.removeSubscriber(d.w)
continue _ = d.w.CloseNow()
}
continue continue
} }
log.D.C( log.D.C(
func() string { func() string {
return fmt.Sprintf( return fmt.Sprintf(
"dispatched event %0x to subscription %s, %s", "dispatched event %0x to subscription %s, %s",
ev.ID, id, subscriber.remote, ev.ID, d.id, d.sub.remote,
) )
}, },
) )
} }
} }
}
// removeSubscriberId removes a specific subscription from a subscriber // removeSubscriberId removes a specific subscription from a subscriber
// websocket. // websocket.
func (p *P) removeSubscriberId(ws *websocket.Conn, id string) { func (p *P) removeSubscriberId(ws *websocket.Conn, id string) {
p.Mx.Lock() p.Mx.Lock()
defer p.Mx.Unlock()
var subs map[string]Subscription var subs map[string]Subscription
var ok bool var ok bool
if subs, ok = p.Map[ws]; ok { if subs, ok = p.Map[ws]; ok {
@@ -208,13 +245,12 @@ func (p *P) removeSubscriberId(ws *websocket.Conn, id string) {
delete(p.Map, ws) delete(p.Map, ws)
} }
} }
p.Mx.Unlock()
} }
// removeSubscriber removes a websocket from the P collection. // removeSubscriber removes a websocket from the P collection.
func (p *P) removeSubscriber(ws *websocket.Conn) { func (p *P) removeSubscriber(ws *websocket.Conn) {
p.Mx.Lock() p.Mx.Lock()
defer p.Mx.Unlock()
clear(p.Map[ws]) clear(p.Map[ws])
delete(p.Map, ws) delete(p.Map, ws)
p.Mx.Unlock()
} }

View File

@@ -15,7 +15,7 @@ import (
type Signer struct { type Signer struct {
SecretKey *secp256k1.SecretKey SecretKey *secp256k1.SecretKey
PublicKey *secp256k1.PublicKey PublicKey *secp256k1.PublicKey
BTCECSec *ec.SecretKey BTCECSec *secp256k1.SecretKey
pkb, skb []byte pkb, skb []byte
} }
@@ -23,11 +23,11 @@ var _ signer.I = &Signer{}
// Generate creates a new Signer. // Generate creates a new Signer.
func (s *Signer) Generate() (err error) { func (s *Signer) Generate() (err error) {
if s.SecretKey, err = ec.NewSecretKey(); chk.E(err) { if s.SecretKey, err = secp256k1.GenerateSecretKey(); chk.E(err) {
return return
} }
s.skb = s.SecretKey.Serialize() s.skb = s.SecretKey.Serialize()
s.BTCECSec, _ = ec.PrivKeyFromBytes(s.skb) s.BTCECSec = secp256k1.PrivKeyFromBytes(s.skb)
s.PublicKey = s.SecretKey.PubKey() s.PublicKey = s.SecretKey.PubKey()
s.pkb = schnorr.SerializePubKey(s.PublicKey) s.pkb = schnorr.SerializePubKey(s.PublicKey)
return return
@@ -43,7 +43,7 @@ func (s *Signer) InitSec(sec []byte) (err error) {
s.SecretKey = secp256k1.SecKeyFromBytes(sec) s.SecretKey = secp256k1.SecKeyFromBytes(sec)
s.PublicKey = s.SecretKey.PubKey() s.PublicKey = s.SecretKey.PubKey()
s.pkb = schnorr.SerializePubKey(s.PublicKey) s.pkb = schnorr.SerializePubKey(s.PublicKey)
s.BTCECSec, _ = ec.PrivKeyFromBytes(s.skb) s.BTCECSec = secp256k1.PrivKeyFromBytes(s.skb)
return return
} }
@@ -142,7 +142,7 @@ func (s *Signer) ECDH(pubkeyBytes []byte) (secret []byte, err error) {
); chk.E(err) { ); chk.E(err) {
return return
} }
secret = ec.GenerateSharedSecret(s.BTCECSec, pub) secret = secp256k1.GenerateSharedSecret(s.BTCECSec, pub)
return return
} }
@@ -154,7 +154,7 @@ type Keygen struct {
// Generate a new key pair. If the result is suitable, the embedded Signer can have its contents // Generate a new key pair. If the result is suitable, the embedded Signer can have its contents
// extracted. // extracted.
func (k *Keygen) Generate() (pubBytes []byte, err error) { func (k *Keygen) Generate() (pubBytes []byte, err error) {
if k.Signer.SecretKey, err = ec.NewSecretKey(); chk.E(err) { if k.Signer.SecretKey, err = secp256k1.GenerateSecretKey(); chk.E(err) {
return return
} }
k.Signer.PublicKey = k.SecretKey.PubKey() k.Signer.PublicKey = k.SecretKey.PubKey()

View File

@@ -1 +1 @@
v0.2.0 v0.2.1