Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
62f244d114
|
|||
|
88ebf6eccc
|
|||
|
4f97cb9a42
|
@@ -75,9 +75,7 @@ func (l *Listener) HandleMessage(msg []byte, remote string) {
|
||||
// Validate message for invalid characters before processing
|
||||
if err := validateJSONMessage(msg); err != nil {
|
||||
log.E.F("%s message validation FAILED (len=%d): %v", remote, len(msg), err)
|
||||
// Don't log the actual message content as it contains binary data
|
||||
// Send generic error notice to client
|
||||
if noticeErr := noticeenvelope.NewFrom("invalid message format: contains invalid characters").Write(l); noticeErr != nil {
|
||||
if noticeErr := noticeenvelope.NewFrom(fmt.Sprintf("invalid message format: contains invalid characters: %s", msg)).Write(l); noticeErr != nil {
|
||||
log.E.F("%s failed to send validation error notice: %v", remote, noticeErr)
|
||||
}
|
||||
return
|
||||
|
||||
@@ -283,13 +283,13 @@ func (l *Listener) HandleReq(msg []byte) (err error) {
|
||||
if !authorized {
|
||||
continue // not authorized to see this private event
|
||||
}
|
||||
|
||||
tmp = append(tmp, ev)
|
||||
continue
|
||||
// Event has private tag and user is authorized - continue to privileged check
|
||||
}
|
||||
|
||||
if l.Config.ACLMode != "none" &&
|
||||
kind.IsPrivileged(ev.Kind) && accessLevel != "admin" { // admins can see all events
|
||||
// Always filter privileged events based on kind, regardless of ACLMode
|
||||
// Privileged events should only be sent to users who are authenticated and
|
||||
// are either the event author or listed in p tags
|
||||
if kind.IsPrivileged(ev.Kind) && accessLevel != "admin" { // admins can see all events
|
||||
log.T.C(
|
||||
func() string {
|
||||
return fmt.Sprintf(
|
||||
@@ -384,27 +384,28 @@ func (l *Listener) HandleReq(msg []byte) (err error) {
|
||||
}
|
||||
|
||||
// Deduplicate events (in case chunk processing returned duplicates)
|
||||
if len(allEvents) > 0 {
|
||||
// Use events (already filtered for privileged/policy) instead of allEvents
|
||||
if len(events) > 0 {
|
||||
seen := make(map[string]struct{})
|
||||
var deduplicatedEvents event.S
|
||||
originalCount := len(allEvents)
|
||||
for _, ev := range allEvents {
|
||||
originalCount := len(events)
|
||||
for _, ev := range events {
|
||||
eventID := hexenc.Enc(ev.ID)
|
||||
if _, exists := seen[eventID]; !exists {
|
||||
seen[eventID] = struct{}{}
|
||||
deduplicatedEvents = append(deduplicatedEvents, ev)
|
||||
}
|
||||
}
|
||||
allEvents = deduplicatedEvents
|
||||
if originalCount != len(allEvents) {
|
||||
log.T.F("REQ %s: deduplicated %d events to %d unique events", env.Subscription, originalCount, len(allEvents))
|
||||
events = deduplicatedEvents
|
||||
if originalCount != len(events) {
|
||||
log.T.F("REQ %s: deduplicated %d events to %d unique events", env.Subscription, originalCount, len(events))
|
||||
}
|
||||
}
|
||||
|
||||
// Apply managed ACL filtering for read access if managed ACL is active
|
||||
if acl.Registry.Active.Load() == "managed" {
|
||||
var aclFilteredEvents event.S
|
||||
for _, ev := range allEvents {
|
||||
for _, ev := range events {
|
||||
// Check if event is banned
|
||||
eventID := hex.EncodeToString(ev.ID)
|
||||
if banned, err := l.getManagedACL().IsEventBanned(eventID); err == nil && banned {
|
||||
@@ -430,13 +431,13 @@ func (l *Listener) HandleReq(msg []byte) (err error) {
|
||||
|
||||
aclFilteredEvents = append(aclFilteredEvents, ev)
|
||||
}
|
||||
allEvents = aclFilteredEvents
|
||||
events = aclFilteredEvents
|
||||
}
|
||||
|
||||
// Apply private tag filtering - only show events with "private" tags to authorized users
|
||||
var privateFilteredEvents event.S
|
||||
authedPubkey := l.authedPubkey.Load()
|
||||
for _, ev := range allEvents {
|
||||
for _, ev := range events {
|
||||
// Check if event has private tags
|
||||
hasPrivateTag := false
|
||||
var privatePubkey []byte
|
||||
@@ -469,10 +470,10 @@ func (l *Listener) HandleReq(msg []byte) (err error) {
|
||||
log.D.F("private tag: filtering out event %s from unauthorized user", hexenc.Enc(ev.ID))
|
||||
}
|
||||
}
|
||||
allEvents = privateFilteredEvents
|
||||
events = privateFilteredEvents
|
||||
|
||||
seen := make(map[string]struct{})
|
||||
for _, ev := range allEvents {
|
||||
for _, ev := range events {
|
||||
log.T.C(
|
||||
func() string {
|
||||
return fmt.Sprintf(
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/coder/websocket"
|
||||
"github.com/gorilla/websocket"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/log"
|
||||
"next.orly.dev/pkg/encoders/envelopes/authenvelope"
|
||||
@@ -24,21 +24,16 @@ const (
|
||||
// ClientMessageSizeLimit is the maximum message size that clients can handle
|
||||
// This is set to 100MB to allow large messages
|
||||
ClientMessageSizeLimit = 100 * 1024 * 1024 // 100MB
|
||||
|
||||
// CloseMessage denotes a close control message. The optional message
|
||||
// payload contains a numeric code and text. Use the FormatCloseMessage
|
||||
// function to format a close message payload.
|
||||
CloseMessage = 8
|
||||
|
||||
// PingMessage denotes a ping control message. The optional message payload
|
||||
// is UTF-8 encoded text.
|
||||
PingMessage = 9
|
||||
|
||||
// PongMessage denotes a pong control message. The optional message payload
|
||||
// is UTF-8 encoded text.
|
||||
PongMessage = 10
|
||||
)
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true // Allow all origins for proxy compatibility
|
||||
},
|
||||
}
|
||||
|
||||
func (s *Server) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
||||
remote := GetRemoteFromReq(r)
|
||||
|
||||
@@ -62,16 +57,12 @@ whitelist:
|
||||
defer cancel()
|
||||
var err error
|
||||
var conn *websocket.Conn
|
||||
// Configure WebSocket accept options for proxy compatibility
|
||||
acceptOptions := &websocket.AcceptOptions{
|
||||
OriginPatterns: []string{"*"}, // Allow all origins for proxy compatibility
|
||||
// Don't check origin when behind a proxy - let the proxy handle it
|
||||
InsecureSkipVerify: true,
|
||||
// Try to set a higher compression threshold to allow larger messages
|
||||
CompressionMode: websocket.CompressionDisabled,
|
||||
}
|
||||
|
||||
if conn, err = websocket.Accept(w, r, acceptOptions); chk.E(err) {
|
||||
// Configure upgrader for this connection
|
||||
upgrader.ReadBufferSize = int(DefaultMaxMessageSize)
|
||||
upgrader.WriteBufferSize = int(DefaultMaxMessageSize)
|
||||
|
||||
if conn, err = upgrader.Upgrade(w, r, nil); chk.E(err) {
|
||||
log.E.F("websocket accept failed from %s: %v", remote, err)
|
||||
return
|
||||
}
|
||||
@@ -80,7 +71,7 @@ whitelist:
|
||||
// Set read limit immediately after connection is established
|
||||
conn.SetReadLimit(DefaultMaxMessageSize)
|
||||
log.D.F("set read limit to %d bytes (%d MB) for %s", DefaultMaxMessageSize, DefaultMaxMessageSize/units.Mb, remote)
|
||||
defer conn.CloseNow()
|
||||
defer conn.Close()
|
||||
listener := &Listener{
|
||||
ctx: ctx,
|
||||
Server: s,
|
||||
@@ -109,6 +100,16 @@ whitelist:
|
||||
log.D.F("AUTH challenge sent successfully to %s", remote)
|
||||
}
|
||||
ticker := time.NewTicker(DefaultPingWait)
|
||||
// Set pong handler
|
||||
conn.SetPongHandler(func(string) error {
|
||||
conn.SetReadDeadline(time.Now().Add(DefaultPongWait))
|
||||
return nil
|
||||
})
|
||||
// Set ping handler
|
||||
conn.SetPingHandler(func(string) error {
|
||||
conn.SetReadDeadline(time.Now().Add(DefaultPongWait))
|
||||
return conn.WriteControl(websocket.PongMessage, []byte{}, time.Now().Add(DefaultWriteTimeout))
|
||||
})
|
||||
// Don't pass cancel to Pinger - it should not be able to cancel the connection context
|
||||
go s.Pinger(ctx, conn, ticker)
|
||||
defer func() {
|
||||
@@ -154,12 +155,19 @@ whitelist:
|
||||
return
|
||||
}
|
||||
|
||||
var typ websocket.MessageType
|
||||
var typ int
|
||||
var msg []byte
|
||||
log.T.F("waiting for message from %s", remote)
|
||||
|
||||
// Set read deadline for context cancellation
|
||||
deadline := time.Now().Add(DefaultPongWait)
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
conn.SetReadDeadline(deadline)
|
||||
|
||||
// Block waiting for message; rely on pings and context cancellation to detect dead peers
|
||||
typ, msg, err = conn.Read(ctx)
|
||||
typ, msg, err = conn.ReadMessage()
|
||||
|
||||
if err != nil {
|
||||
// Check if the error is due to context cancellation
|
||||
@@ -180,50 +188,40 @@ whitelist:
|
||||
return
|
||||
}
|
||||
// Handle message too big errors specifically
|
||||
if strings.Contains(err.Error(), "MessageTooBig") ||
|
||||
if strings.Contains(err.Error(), "message too large") ||
|
||||
strings.Contains(err.Error(), "read limited at") {
|
||||
log.D.F("client %s hit message size limit: %v", remote, err)
|
||||
// Don't log this as an error since it's a client-side limit
|
||||
// Just close the connection gracefully
|
||||
return
|
||||
}
|
||||
status := websocket.CloseStatus(err)
|
||||
switch status {
|
||||
case websocket.StatusNormalClosure,
|
||||
websocket.StatusGoingAway,
|
||||
websocket.StatusNoStatusRcvd,
|
||||
websocket.StatusAbnormalClosure,
|
||||
websocket.StatusProtocolError:
|
||||
log.T.F(
|
||||
"connection from %s closed with status: %v", remote, status,
|
||||
)
|
||||
case websocket.StatusMessageTooBig:
|
||||
// Check for websocket close errors
|
||||
if websocket.IsCloseError(err, websocket.CloseNormalClosure,
|
||||
websocket.CloseGoingAway,
|
||||
websocket.CloseNoStatusReceived,
|
||||
websocket.CloseAbnormalClosure,
|
||||
websocket.CloseUnsupportedData,
|
||||
websocket.CloseInvalidFramePayloadData) {
|
||||
log.T.F("connection from %s closed: %v", remote, err)
|
||||
} else if websocket.IsCloseError(err, websocket.CloseMessageTooBig) {
|
||||
log.D.F("client %s sent message too big: %v", remote, err)
|
||||
default:
|
||||
} else {
|
||||
log.E.F("unexpected close error from %s: %v", remote, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if typ == PingMessage {
|
||||
if typ == websocket.PingMessage {
|
||||
log.D.F("received PING from %s, sending PONG", remote)
|
||||
// Create a write context with timeout for pong response
|
||||
writeCtx, writeCancel := context.WithTimeout(
|
||||
ctx, DefaultWriteTimeout,
|
||||
)
|
||||
deadline := time.Now().Add(DefaultWriteTimeout)
|
||||
conn.SetWriteDeadline(deadline)
|
||||
pongStart := time.Now()
|
||||
if err = conn.Write(writeCtx, PongMessage, msg); chk.E(err) {
|
||||
if err = conn.WriteControl(websocket.PongMessage, msg, deadline); chk.E(err) {
|
||||
pongDuration := time.Since(pongStart)
|
||||
log.E.F(
|
||||
"failed to send PONG to %s after %v: %v", remote,
|
||||
pongDuration, err,
|
||||
)
|
||||
if writeCtx.Err() != nil {
|
||||
log.E.F(
|
||||
"PONG write timeout to %s after %v (limit=%v)", remote,
|
||||
pongDuration, DefaultWriteTimeout,
|
||||
)
|
||||
}
|
||||
writeCancel()
|
||||
return
|
||||
}
|
||||
pongDuration := time.Since(pongStart)
|
||||
@@ -231,7 +229,6 @@ whitelist:
|
||||
if pongDuration > time.Millisecond*50 {
|
||||
log.D.F("SLOW PONG to %s: %v (>50ms)", remote, pongDuration)
|
||||
}
|
||||
writeCancel()
|
||||
continue
|
||||
}
|
||||
// Log message size for debugging
|
||||
@@ -260,26 +257,18 @@ func (s *Server) Pinger(
|
||||
pingCount++
|
||||
log.D.F("sending PING #%d", pingCount)
|
||||
|
||||
// Create a write context with timeout for ping operation
|
||||
pingCtx, pingCancel := context.WithTimeout(ctx, DefaultWriteTimeout)
|
||||
// Set write deadline for ping operation
|
||||
deadline := time.Now().Add(DefaultWriteTimeout)
|
||||
conn.SetWriteDeadline(deadline)
|
||||
pingStart := time.Now()
|
||||
|
||||
if err = conn.Ping(pingCtx); err != nil {
|
||||
if err = conn.WriteControl(websocket.PingMessage, []byte{}, deadline); err != nil {
|
||||
pingDuration := time.Since(pingStart)
|
||||
log.E.F(
|
||||
"PING #%d FAILED after %v: %v", pingCount, pingDuration,
|
||||
err,
|
||||
)
|
||||
|
||||
if pingCtx.Err() != nil {
|
||||
log.E.F(
|
||||
"PING #%d timeout after %v (limit=%v)", pingCount,
|
||||
pingDuration, DefaultWriteTimeout,
|
||||
)
|
||||
}
|
||||
|
||||
chk.E(err)
|
||||
pingCancel()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -289,8 +278,6 @@ func (s *Server) Pinger(
|
||||
if pingDuration > time.Millisecond*100 {
|
||||
log.D.F("SLOW PING #%d: %v (>100ms)", pingCount, pingDuration)
|
||||
}
|
||||
|
||||
pingCancel()
|
||||
case <-ctx.Done():
|
||||
log.T.F("pinger context cancelled after %d pings", pingCount)
|
||||
return
|
||||
|
||||
@@ -3,9 +3,10 @@ package app
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/coder/websocket"
|
||||
"github.com/gorilla/websocket"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/log"
|
||||
"next.orly.dev/pkg/acl"
|
||||
@@ -54,14 +55,12 @@ func (l *Listener) Write(p []byte) (n int, err error) {
|
||||
|
||||
// Use a separate context with timeout for writes to prevent race conditions
|
||||
// where the main connection context gets cancelled while writing events
|
||||
writeCtx, cancel := context.WithTimeout(
|
||||
context.Background(), DefaultWriteTimeout,
|
||||
)
|
||||
defer cancel()
|
||||
deadline := time.Now().Add(DefaultWriteTimeout)
|
||||
l.conn.SetWriteDeadline(deadline)
|
||||
|
||||
// Attempt the write operation
|
||||
writeStart := time.Now()
|
||||
if err = l.conn.Write(writeCtx, websocket.MessageText, p); err != nil {
|
||||
if err = l.conn.WriteMessage(websocket.TextMessage, p); err != nil {
|
||||
writeDuration := time.Since(writeStart)
|
||||
totalDuration := time.Since(start)
|
||||
|
||||
@@ -72,7 +71,7 @@ func (l *Listener) Write(p []byte) (n int, err error) {
|
||||
)
|
||||
|
||||
// Check if this is a context timeout
|
||||
if writeCtx.Err() != nil {
|
||||
if strings.Contains(err.Error(), "timeout") || strings.Contains(err.Error(), "deadline") {
|
||||
log.E.F(
|
||||
"ws->%s write timeout after %v (limit=%v)", l.remote,
|
||||
writeDuration, DefaultWriteTimeout,
|
||||
|
||||
@@ -3,10 +3,11 @@ package app
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coder/websocket"
|
||||
"github.com/gorilla/websocket"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/log"
|
||||
"next.orly.dev/pkg/acl"
|
||||
@@ -270,15 +271,11 @@ func (p *P) Deliver(ev *event.E) {
|
||||
|
||||
// Use a separate context with timeout for writes to prevent race conditions
|
||||
// where the publisher context gets cancelled while writing events
|
||||
writeCtx, cancel := context.WithTimeout(
|
||||
context.Background(), DefaultWriteTimeout,
|
||||
)
|
||||
defer cancel()
|
||||
deadline := time.Now().Add(DefaultWriteTimeout)
|
||||
d.w.SetWriteDeadline(deadline)
|
||||
|
||||
deliveryStart := time.Now()
|
||||
if err = d.w.Write(
|
||||
writeCtx, websocket.MessageText, msgData,
|
||||
); err != nil {
|
||||
if err = d.w.WriteMessage(websocket.TextMessage, msgData); err != nil {
|
||||
deliveryDuration := time.Since(deliveryStart)
|
||||
|
||||
// Log detailed failure information
|
||||
@@ -286,7 +283,7 @@ func (p *P) Deliver(ev *event.E) {
|
||||
hex.Enc(ev.ID), d.sub.remote, d.id, deliveryDuration, err)
|
||||
|
||||
// Check for timeout specifically
|
||||
if writeCtx.Err() != nil {
|
||||
if strings.Contains(err.Error(), "timeout") || strings.Contains(err.Error(), "deadline") {
|
||||
log.E.F("subscription delivery TIMEOUT: event=%s to=%s after %v (limit=%v)",
|
||||
hex.Enc(ev.ID), d.sub.remote, deliveryDuration, DefaultWriteTimeout)
|
||||
}
|
||||
@@ -296,7 +293,7 @@ func (p *P) Deliver(ev *event.E) {
|
||||
|
||||
// On error, remove the subscriber connection safely
|
||||
p.removeSubscriber(d.w)
|
||||
_ = d.w.CloseNow()
|
||||
_ = d.w.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
2
go.mod
2
go.mod
@@ -4,9 +4,9 @@ go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/adrg/xdg v0.5.3
|
||||
github.com/coder/websocket v1.8.14
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/dgraph-io/badger/v4 v4.8.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
|
||||
github.com/klauspost/cpuid/v2 v2.3.0
|
||||
github.com/pkg/profile v1.7.0
|
||||
|
||||
4
go.sum
4
go.sum
@@ -13,8 +13,6 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
|
||||
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
||||
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -45,6 +43,8 @@ github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8I
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/pprof v0.0.0-20251007162407-5df77e3f7d1d h1:KJIErDwbSHjnp/SGzE5ed8Aol7JsKiI5X7yWKAtzhM0=
|
||||
github.com/google/pprof v0.0.0-20251007162407-5df77e3f7d1d/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coder/websocket"
|
||||
"github.com/gorilla/websocket"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/errorf"
|
||||
"lol.mleku.dev/log"
|
||||
@@ -396,12 +396,15 @@ func (f *Follows) startEventSubscriptions(ctx context.Context) {
|
||||
headers.Set("Origin", "https://orly.dev")
|
||||
|
||||
// Use proper WebSocket dial options
|
||||
dialOptions := &websocket.DialOptions{
|
||||
HTTPHeader: headers,
|
||||
dialer := websocket.Dialer{
|
||||
HandshakeTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
c, _, err := websocket.Dial(connCtx, u, dialOptions)
|
||||
c, resp, err := dialer.DialContext(connCtx, u, headers)
|
||||
cancel()
|
||||
if resp != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
log.W.F("follows syncer: dial %s failed: %v", u, err)
|
||||
|
||||
@@ -480,13 +483,12 @@ func (f *Follows) startEventSubscriptions(ctx context.Context) {
|
||||
req := reqenvelope.NewFrom([]byte(subID), ff)
|
||||
reqBytes := req.Marshal(nil)
|
||||
log.T.F("follows syncer: outbound REQ to %s: %s", u, string(reqBytes))
|
||||
if err = c.Write(
|
||||
ctx, websocket.MessageText, reqBytes,
|
||||
); chk.E(err) {
|
||||
c.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||
if err = c.WriteMessage(websocket.TextMessage, reqBytes); chk.E(err) {
|
||||
log.W.F(
|
||||
"follows syncer: failed to send event REQ to %s: %v", u, err,
|
||||
)
|
||||
_ = c.Close(websocket.StatusInternalError, "write failed")
|
||||
_ = c.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseInternalServerErr, "write failed"), time.Now().Add(time.Second))
|
||||
continue
|
||||
}
|
||||
log.T.F(
|
||||
@@ -501,11 +503,12 @@ func (f *Follows) startEventSubscriptions(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
_ = c.Close(websocket.StatusNormalClosure, "ctx done")
|
||||
_ = c.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "ctx done"), time.Now().Add(time.Second))
|
||||
return
|
||||
case <-keepaliveTicker.C:
|
||||
// Send ping to keep connection alive
|
||||
if err := c.Ping(ctx); err != nil {
|
||||
c.SetWriteDeadline(time.Now().Add(5 * time.Second))
|
||||
if err := c.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(5*time.Second)); err != nil {
|
||||
log.T.F("follows syncer: ping failed for %s: %v", u, err)
|
||||
break readLoop
|
||||
}
|
||||
@@ -513,11 +516,10 @@ func (f *Follows) startEventSubscriptions(ctx context.Context) {
|
||||
continue
|
||||
default:
|
||||
// Set a read timeout to avoid hanging
|
||||
readCtx, readCancel := context.WithTimeout(ctx, 60*time.Second)
|
||||
_, data, err := c.Read(readCtx)
|
||||
readCancel()
|
||||
c.SetReadDeadline(time.Now().Add(60 * time.Second))
|
||||
_, data, err := c.ReadMessage()
|
||||
if err != nil {
|
||||
_ = c.Close(websocket.StatusNormalClosure, "read err")
|
||||
_ = c.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "read err"), time.Now().Add(time.Second))
|
||||
break readLoop
|
||||
}
|
||||
label, rem, err := envelopes.Identify(data)
|
||||
@@ -714,16 +716,19 @@ func (f *Follows) fetchFollowListsFromRelay(relayURL string, authors [][]byte) {
|
||||
headers.Set("Origin", "https://orly.dev")
|
||||
|
||||
// Use proper WebSocket dial options
|
||||
dialOptions := &websocket.DialOptions{
|
||||
HTTPHeader: headers,
|
||||
dialer := websocket.Dialer{
|
||||
HandshakeTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
c, _, err := websocket.Dial(ctx, relayURL, dialOptions)
|
||||
c, resp, err := dialer.DialContext(ctx, relayURL, headers)
|
||||
if resp != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
log.W.F("follows syncer: failed to connect to %s for follow list fetch: %v", relayURL, err)
|
||||
return
|
||||
}
|
||||
defer c.Close(websocket.StatusNormalClosure, "follow list fetch complete")
|
||||
defer c.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "follow list fetch complete"), time.Now().Add(time.Second))
|
||||
|
||||
log.I.F("follows syncer: fetching follow lists from relay %s", relayURL)
|
||||
|
||||
@@ -746,7 +751,8 @@ func (f *Follows) fetchFollowListsFromRelay(relayURL string, authors [][]byte) {
|
||||
req := reqenvelope.NewFrom([]byte(subID), ff)
|
||||
reqBytes := req.Marshal(nil)
|
||||
log.T.F("follows syncer: outbound REQ to %s: %s", relayURL, string(reqBytes))
|
||||
if err = c.Write(ctx, websocket.MessageText, reqBytes); chk.E(err) {
|
||||
c.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||
if err = c.WriteMessage(websocket.TextMessage, reqBytes); chk.E(err) {
|
||||
log.W.F("follows syncer: failed to send follow list REQ to %s: %v", relayURL, err)
|
||||
return
|
||||
}
|
||||
@@ -769,7 +775,8 @@ func (f *Follows) fetchFollowListsFromRelay(relayURL string, authors [][]byte) {
|
||||
default:
|
||||
}
|
||||
|
||||
_, data, err := c.Read(ctx)
|
||||
c.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||
_, data, err := c.ReadMessage()
|
||||
if err != nil {
|
||||
log.T.F("follows syncer: error reading events from %s: %v", relayURL, err)
|
||||
goto processEvents
|
||||
|
||||
@@ -144,6 +144,7 @@ func (t *T) Key() (key []byte) {
|
||||
}
|
||||
|
||||
func (t *T) Value() (key []byte) {
|
||||
if t==nil {return}
|
||||
if len(t.T) > Value {
|
||||
return t.T[Value]
|
||||
}
|
||||
|
||||
@@ -8,20 +8,27 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"lol.mleku.dev/chk"
|
||||
"next.orly.dev/pkg/crypto/p256k"
|
||||
"next.orly.dev/pkg/encoders/event"
|
||||
"next.orly.dev/pkg/encoders/hex"
|
||||
"next.orly.dev/pkg/encoders/tag"
|
||||
)
|
||||
|
||||
// Helper function to create test event
|
||||
func createTestEventBench(id, pubkey, content string, kind uint16) *event.E {
|
||||
return &event.E{
|
||||
ID: []byte(id),
|
||||
Kind: kind,
|
||||
Pubkey: []byte(pubkey),
|
||||
Content: []byte(content),
|
||||
Tags: &tag.S{},
|
||||
CreatedAt: time.Now().Unix(),
|
||||
// Helper function to create test event for benchmarks (reuses signer)
|
||||
func createTestEventBench(b *testing.B, signer *p256k.Signer, content string, kind uint16) *event.E {
|
||||
ev := event.New()
|
||||
ev.CreatedAt = time.Now().Unix()
|
||||
ev.Kind = kind
|
||||
ev.Content = []byte(content)
|
||||
ev.Tags = tag.NewS()
|
||||
|
||||
// Sign the event properly
|
||||
if err := ev.Sign(signer); chk.E(err) {
|
||||
b.Fatalf("Failed to sign test event: %v", err)
|
||||
}
|
||||
|
||||
return ev
|
||||
}
|
||||
|
||||
func BenchmarkCheckKindsPolicy(b *testing.B) {
|
||||
@@ -38,12 +45,13 @@ func BenchmarkCheckKindsPolicy(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkCheckRulePolicy(b *testing.B) {
|
||||
// Create test event
|
||||
testEvent := createTestEventBench("test-event-id", "test-pubkey", "test content", 1)
|
||||
// Generate keypair once for all events
|
||||
signer, pubkey := generateTestKeypairB(b)
|
||||
testEvent := createTestEventBench(b, signer, "test content", 1)
|
||||
|
||||
rule := Rule{
|
||||
Description: "test rule",
|
||||
WriteAllow: []string{"test-pubkey"},
|
||||
WriteAllow: []string{hex.Enc(pubkey)},
|
||||
SizeLimit: int64Ptr(10000),
|
||||
ContentLimit: int64Ptr(1000),
|
||||
MustHaveTags: []string{"p"},
|
||||
@@ -53,13 +61,14 @@ func BenchmarkCheckRulePolicy(b *testing.B) {
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
policy.checkRulePolicy("write", testEvent, rule, []byte("test-pubkey"))
|
||||
policy.checkRulePolicy("write", testEvent, rule, pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCheckPolicy(b *testing.B) {
|
||||
// Create test event
|
||||
testEvent := createTestEventBench("test-event-id", "test-pubkey", "test content", 1)
|
||||
// Generate keypair once for all events
|
||||
signer, pubkey := generateTestKeypairB(b)
|
||||
testEvent := createTestEventBench(b, signer, "test content", 1)
|
||||
|
||||
policy := &P{
|
||||
Kind: Kinds{
|
||||
@@ -68,14 +77,14 @@ func BenchmarkCheckPolicy(b *testing.B) {
|
||||
Rules: map[int]Rule{
|
||||
1: {
|
||||
Description: "test rule",
|
||||
WriteAllow: []string{"test-pubkey"},
|
||||
WriteAllow: []string{hex.Enc(pubkey)},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
policy.CheckPolicy("write", testEvent, []byte("test-pubkey"), "127.0.0.1")
|
||||
policy.CheckPolicy("write", testEvent, pubkey, "127.0.0.1")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,8 +123,9 @@ done
|
||||
// Give the script time to start
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Create test event
|
||||
testEvent := createTestEventBench("test-event-id", "test-pubkey", "test content", 1)
|
||||
// Generate keypair once for all events
|
||||
signer, pubkey := generateTestKeypairB(b)
|
||||
testEvent := createTestEventBench(b, signer, "test content", 1)
|
||||
|
||||
policy := &P{
|
||||
Manager: manager,
|
||||
@@ -130,7 +140,7 @@ done
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
policy.CheckPolicy("write", testEvent, []byte("test-pubkey"), "127.0.0.1")
|
||||
policy.CheckPolicy("write", testEvent, pubkey, "127.0.0.1")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,16 +200,19 @@ func BenchmarkCheckPolicyMultipleKinds(b *testing.B) {
|
||||
Rules: rules,
|
||||
}
|
||||
|
||||
// Generate keypair once for all events
|
||||
signer, pubkey := generateTestKeypairB(b)
|
||||
|
||||
// Create test events with different kinds
|
||||
events := make([]*event.E, 100)
|
||||
for i := 0; i < 100; i++ {
|
||||
events[i] = createTestEvent("test-event-id", "test-pubkey", "test content", uint16(i+1))
|
||||
events[i] = createTestEventBench(b, signer, "test content", uint16(i+1))
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
event := events[i%100]
|
||||
policy.CheckPolicy("write", event, []byte("test-pubkey"), "127.0.0.1")
|
||||
policy.CheckPolicy("write", event, pubkey, "127.0.0.1")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,11 +230,13 @@ func BenchmarkCheckPolicyLargeWhitelist(b *testing.B) {
|
||||
Rules: map[int]Rule{},
|
||||
}
|
||||
|
||||
testEvent := createTestEvent("test-event-id", "test-pubkey", "test content", 500) // Kind in the middle of the whitelist
|
||||
// Generate keypair once for all events
|
||||
signer, pubkey := generateTestKeypairB(b)
|
||||
testEvent := createTestEventBench(b, signer, "test content", 500) // Kind in the middle of the whitelist
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
policy.CheckPolicy("write", testEvent, []byte("test-pubkey"), "127.0.0.1")
|
||||
policy.CheckPolicy("write", testEvent, pubkey, "127.0.0.1")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,22 +254,25 @@ func BenchmarkCheckPolicyLargeBlacklist(b *testing.B) {
|
||||
Rules: map[int]Rule{},
|
||||
}
|
||||
|
||||
testEvent := createTestEvent("test-event-id", "test-pubkey", "test content", 1500) // Kind not in blacklist
|
||||
// Generate keypair once for all events
|
||||
signer, pubkey := generateTestKeypairB(b)
|
||||
testEvent := createTestEventBench(b, signer, "test content", 1500) // Kind not in blacklist
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
policy.CheckPolicy("write", testEvent, []byte("test-pubkey"), "127.0.0.1")
|
||||
policy.CheckPolicy("write", testEvent, pubkey, "127.0.0.1")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCheckPolicyComplexRule(b *testing.B) {
|
||||
// Create test event with many tags
|
||||
testEvent := createTestEventBench("test-event-id", "test-pubkey", "test content", 1)
|
||||
// Generate keypair once for all events
|
||||
signer, pubkey := generateTestKeypairB(b)
|
||||
testEvent := createTestEventBench(b, signer, "test content", 1)
|
||||
|
||||
// Add many tags
|
||||
for i := 0; i < 100; i++ {
|
||||
tagItem1 := tag.New()
|
||||
tagItem1.T = append(tagItem1.T, []byte("p"), []byte("test-pubkey"))
|
||||
tagItem1.T = append(tagItem1.T, []byte("p"), []byte(hex.Enc(pubkey)))
|
||||
*testEvent.Tags = append(*testEvent.Tags, tagItem1)
|
||||
|
||||
tagItem2 := tag.New()
|
||||
@@ -264,7 +282,7 @@ func BenchmarkCheckPolicyComplexRule(b *testing.B) {
|
||||
|
||||
rule := Rule{
|
||||
Description: "complex rule",
|
||||
WriteAllow: []string{"test-pubkey"},
|
||||
WriteAllow: []string{hex.Enc(pubkey)},
|
||||
SizeLimit: int64Ptr(100000),
|
||||
ContentLimit: int64Ptr(10000),
|
||||
MustHaveTags: []string{"p", "e"},
|
||||
@@ -275,7 +293,7 @@ func BenchmarkCheckPolicyComplexRule(b *testing.B) {
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
policy.checkRulePolicy("write", testEvent, rule, []byte("test-pubkey"))
|
||||
policy.checkRulePolicy("write", testEvent, rule, pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,11 +312,12 @@ func BenchmarkCheckPolicyLargeEvent(b *testing.B) {
|
||||
},
|
||||
}
|
||||
|
||||
// Create test event with large content
|
||||
testEvent := createTestEvent("test-event-id", "test-pubkey", largeContent, 1)
|
||||
// Generate keypair once for all events
|
||||
signer, pubkey := generateTestKeypairB(b)
|
||||
testEvent := createTestEventBench(b, signer, largeContent, 1)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
policy.CheckPolicy("write", testEvent, []byte("test-pubkey"), "127.0.0.1")
|
||||
policy.CheckPolicy("write", testEvent, pubkey, "127.0.0.1")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"lol.mleku.dev/chk"
|
||||
"next.orly.dev/pkg/crypto/p256k"
|
||||
"next.orly.dev/pkg/encoders/event"
|
||||
"next.orly.dev/pkg/encoders/hex"
|
||||
"next.orly.dev/pkg/encoders/tag"
|
||||
@@ -19,23 +21,68 @@ func int64Ptr(i int64) *int64 {
|
||||
return &i
|
||||
}
|
||||
|
||||
// Helper function to create test event
|
||||
func createTestEvent(id, pubkey, content string, kind uint16) *event.E {
|
||||
return &event.E{
|
||||
ID: []byte(id),
|
||||
Kind: kind,
|
||||
Pubkey: []byte(pubkey),
|
||||
Content: []byte(content),
|
||||
Tags: &tag.S{},
|
||||
CreatedAt: time.Now().Unix(),
|
||||
// Helper function to generate a keypair for testing
|
||||
func generateTestKeypair(t *testing.T) (signer *p256k.Signer, pubkey []byte) {
|
||||
signer = &p256k.Signer{}
|
||||
if err := signer.Generate(); chk.E(err) {
|
||||
t.Fatalf("Failed to generate test keypair: %v", err)
|
||||
}
|
||||
pubkey = signer.Pub()
|
||||
return
|
||||
}
|
||||
|
||||
// Helper function to add tags to event
|
||||
// Helper function to generate a keypair for benchmarks
|
||||
func generateTestKeypairB(b *testing.B) (signer *p256k.Signer, pubkey []byte) {
|
||||
signer = &p256k.Signer{}
|
||||
if err := signer.Generate(); chk.E(err) {
|
||||
b.Fatalf("Failed to generate test keypair: %v", err)
|
||||
}
|
||||
pubkey = signer.Pub()
|
||||
return
|
||||
}
|
||||
|
||||
// Helper function to create a real test event with proper signing
|
||||
func createTestEvent(t *testing.T, signer *p256k.Signer, content string, kind uint16) *event.E {
|
||||
ev := event.New()
|
||||
ev.CreatedAt = time.Now().Unix()
|
||||
ev.Kind = kind
|
||||
ev.Content = []byte(content)
|
||||
ev.Tags = tag.NewS()
|
||||
|
||||
// Sign the event properly
|
||||
if err := ev.Sign(signer); chk.E(err) {
|
||||
t.Fatalf("Failed to sign test event: %v", err)
|
||||
}
|
||||
|
||||
return ev
|
||||
}
|
||||
|
||||
// Helper function to create a test event with a specific pubkey (for unauthorized tests)
|
||||
func createTestEventWithPubkey(t *testing.T, signer *p256k.Signer, content string, kind uint16) *event.E {
|
||||
ev := event.New()
|
||||
ev.CreatedAt = time.Now().Unix()
|
||||
ev.Kind = kind
|
||||
ev.Content = []byte(content)
|
||||
ev.Tags = tag.NewS()
|
||||
|
||||
// Sign the event properly
|
||||
if err := ev.Sign(signer); chk.E(err) {
|
||||
t.Fatalf("Failed to sign test event: %v", err)
|
||||
}
|
||||
|
||||
return ev
|
||||
}
|
||||
|
||||
// Helper function to add p tag with hex-encoded pubkey to event
|
||||
func addPTag(ev *event.E, pubkey []byte) {
|
||||
pTag := tag.NewFromAny("p", hex.Enc(pubkey))
|
||||
ev.Tags.Append(pTag)
|
||||
}
|
||||
|
||||
// Helper function to add other tags to event
|
||||
func addTag(ev *event.E, key, value string) {
|
||||
tagItem := tag.New()
|
||||
tagItem.T = append(tagItem.T, []byte(key), []byte(value))
|
||||
*ev.Tags = append(*ev.Tags, tagItem)
|
||||
tagItem := tag.NewFromAny(key, value)
|
||||
ev.Tags.Append(tagItem)
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
@@ -174,10 +221,15 @@ func TestCheckKindsPolicy(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCheckRulePolicy(t *testing.T) {
|
||||
// Create test event
|
||||
testEvent := createTestEvent("test-event-id", "test-pubkey", "test content", 1)
|
||||
// Generate real keypairs for testing
|
||||
eventSigner, eventPubkey := generateTestKeypair(t)
|
||||
_, pTagPubkey := generateTestKeypair(t)
|
||||
_, unauthorizedPubkey := generateTestKeypair(t)
|
||||
|
||||
// Create real test event with proper signing
|
||||
testEvent := createTestEvent(t, eventSigner, "test content", 1)
|
||||
// Add p tag with hex-encoded pubkey
|
||||
addTag(testEvent, "p", hex.Enc([]byte("test-pubkey-2")))
|
||||
addPTag(testEvent, pTagPubkey)
|
||||
addTag(testEvent, "expiration", "1234567890")
|
||||
|
||||
tests := []struct {
|
||||
@@ -195,7 +247,7 @@ func TestCheckRulePolicy(t *testing.T) {
|
||||
rule: Rule{
|
||||
Description: "no restrictions",
|
||||
},
|
||||
loggedInPubkey: []byte("test-pubkey"),
|
||||
loggedInPubkey: eventPubkey,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
@@ -206,7 +258,7 @@ func TestCheckRulePolicy(t *testing.T) {
|
||||
Description: "pubkey allowed",
|
||||
WriteAllow: []string{hex.Enc(testEvent.Pubkey)},
|
||||
},
|
||||
loggedInPubkey: []byte("test-pubkey"),
|
||||
loggedInPubkey: eventPubkey,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
@@ -215,9 +267,9 @@ func TestCheckRulePolicy(t *testing.T) {
|
||||
event: testEvent,
|
||||
rule: Rule{
|
||||
Description: "pubkey not allowed",
|
||||
WriteAllow: []string{"other-pubkey"},
|
||||
WriteAllow: []string{hex.Enc(pTagPubkey)}, // Different pubkey
|
||||
},
|
||||
loggedInPubkey: []byte("test-pubkey"),
|
||||
loggedInPubkey: eventPubkey,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
@@ -228,7 +280,7 @@ func TestCheckRulePolicy(t *testing.T) {
|
||||
Description: "size limit",
|
||||
SizeLimit: int64Ptr(10000),
|
||||
},
|
||||
loggedInPubkey: []byte("test-pubkey"),
|
||||
loggedInPubkey: eventPubkey,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
@@ -239,7 +291,7 @@ func TestCheckRulePolicy(t *testing.T) {
|
||||
Description: "size limit exceeded",
|
||||
SizeLimit: int64Ptr(10),
|
||||
},
|
||||
loggedInPubkey: []byte("test-pubkey"),
|
||||
loggedInPubkey: eventPubkey,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
@@ -250,7 +302,7 @@ func TestCheckRulePolicy(t *testing.T) {
|
||||
Description: "content limit",
|
||||
ContentLimit: int64Ptr(1000),
|
||||
},
|
||||
loggedInPubkey: []byte("test-pubkey"),
|
||||
loggedInPubkey: eventPubkey,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
@@ -261,7 +313,7 @@ func TestCheckRulePolicy(t *testing.T) {
|
||||
Description: "content limit exceeded",
|
||||
ContentLimit: int64Ptr(5),
|
||||
},
|
||||
loggedInPubkey: []byte("test-pubkey"),
|
||||
loggedInPubkey: eventPubkey,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
@@ -272,7 +324,7 @@ func TestCheckRulePolicy(t *testing.T) {
|
||||
Description: "required tags",
|
||||
MustHaveTags: []string{"p"},
|
||||
},
|
||||
loggedInPubkey: []byte("test-pubkey"),
|
||||
loggedInPubkey: eventPubkey,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
@@ -283,7 +335,7 @@ func TestCheckRulePolicy(t *testing.T) {
|
||||
Description: "required tags missing",
|
||||
MustHaveTags: []string{"e"},
|
||||
},
|
||||
loggedInPubkey: []byte("test-pubkey"),
|
||||
loggedInPubkey: eventPubkey,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
@@ -305,7 +357,7 @@ func TestCheckRulePolicy(t *testing.T) {
|
||||
Description: "privileged event with p tag",
|
||||
Privileged: true,
|
||||
},
|
||||
loggedInPubkey: []byte("test-pubkey-2"),
|
||||
loggedInPubkey: pTagPubkey,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
@@ -319,6 +371,61 @@ func TestCheckRulePolicy(t *testing.T) {
|
||||
loggedInPubkey: nil,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "privileged - authenticated but not authorized (different pubkey, not in p tags)",
|
||||
access: "write",
|
||||
event: testEvent,
|
||||
rule: Rule{
|
||||
Description: "privileged event unauthorized user",
|
||||
Privileged: true,
|
||||
},
|
||||
loggedInPubkey: unauthorizedPubkey,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "privileged read - event authored by logged in user",
|
||||
access: "read",
|
||||
event: testEvent,
|
||||
rule: Rule{
|
||||
Description: "privileged event read access",
|
||||
Privileged: true,
|
||||
},
|
||||
loggedInPubkey: testEvent.Pubkey,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "privileged read - event contains logged in user in p tag",
|
||||
access: "read",
|
||||
event: testEvent,
|
||||
rule: Rule{
|
||||
Description: "privileged event read access with p tag",
|
||||
Privileged: true,
|
||||
},
|
||||
loggedInPubkey: pTagPubkey,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "privileged read - not authenticated",
|
||||
access: "read",
|
||||
event: testEvent,
|
||||
rule: Rule{
|
||||
Description: "privileged event read access not authenticated",
|
||||
Privileged: true,
|
||||
},
|
||||
loggedInPubkey: nil,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "privileged read - authenticated but not authorized (different pubkey, not in p tags)",
|
||||
access: "read",
|
||||
event: testEvent,
|
||||
rule: Rule{
|
||||
Description: "privileged event read access unauthorized user",
|
||||
Privileged: true,
|
||||
},
|
||||
loggedInPubkey: unauthorizedPubkey,
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -337,8 +444,11 @@ func TestCheckRulePolicy(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCheckPolicy(t *testing.T) {
|
||||
// Create test event
|
||||
testEvent := createTestEvent("test-event-id", "test-pubkey", "test content", 1)
|
||||
// Generate real keypair for testing
|
||||
eventSigner, eventPubkey := generateTestKeypair(t)
|
||||
|
||||
// Create real test event with proper signing
|
||||
testEvent := createTestEvent(t, eventSigner, "test content", 1)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -358,7 +468,7 @@ func TestCheckPolicy(t *testing.T) {
|
||||
Kind: Kinds{},
|
||||
Rules: map[int]Rule{},
|
||||
},
|
||||
loggedInPubkey: []byte("test-pubkey"),
|
||||
loggedInPubkey: eventPubkey,
|
||||
ipAddress: "127.0.0.1",
|
||||
expected: true,
|
||||
expectError: false,
|
||||
@@ -373,7 +483,7 @@ func TestCheckPolicy(t *testing.T) {
|
||||
},
|
||||
Rules: map[int]Rule{},
|
||||
},
|
||||
loggedInPubkey: []byte("test-pubkey"),
|
||||
loggedInPubkey: eventPubkey,
|
||||
ipAddress: "127.0.0.1",
|
||||
expected: false,
|
||||
expectError: false,
|
||||
@@ -391,7 +501,7 @@ func TestCheckPolicy(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
loggedInPubkey: []byte("test-pubkey"),
|
||||
loggedInPubkey: eventPubkey,
|
||||
ipAddress: "127.0.0.1",
|
||||
expected: false,
|
||||
expectError: false,
|
||||
@@ -488,13 +598,16 @@ func TestLoadFromFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPolicyEventSerialization(t *testing.T) {
|
||||
// Create test event
|
||||
testEvent := createTestEvent("test-event-id", "test-pubkey", "test content", 1)
|
||||
// Generate real keypair for testing
|
||||
eventSigner, eventPubkey := generateTestKeypair(t)
|
||||
|
||||
// Create real test event with proper signing
|
||||
testEvent := createTestEvent(t, eventSigner, "test content", 1)
|
||||
|
||||
// Create policy event
|
||||
policyEvent := &PolicyEvent{
|
||||
E: testEvent,
|
||||
LoggedInPubkey: "test-logged-in-pubkey",
|
||||
LoggedInPubkey: hex.Enc(eventPubkey),
|
||||
IPAddress: "127.0.0.1",
|
||||
}
|
||||
|
||||
@@ -508,13 +621,13 @@ func TestPolicyEventSerialization(t *testing.T) {
|
||||
jsonStr := string(jsonData)
|
||||
t.Logf("Generated JSON: %s", jsonStr)
|
||||
|
||||
if !strings.Contains(jsonStr, "test-logged-in-pubkey") {
|
||||
if !strings.Contains(jsonStr, hex.Enc(eventPubkey)) {
|
||||
t.Error("JSON should contain logged_in_pubkey field")
|
||||
}
|
||||
if !strings.Contains(jsonStr, "127.0.0.1") {
|
||||
t.Error("JSON should contain ip_address field")
|
||||
}
|
||||
if !strings.Contains(jsonStr, "746573742d6576656e742d6964") { // hex encoded "test-event-id"
|
||||
if !strings.Contains(jsonStr, hex.Enc(testEvent.ID)) {
|
||||
t.Error("JSON should contain event id field (hex encoded)")
|
||||
}
|
||||
|
||||
@@ -646,13 +759,16 @@ func TestPolicyManagerProcessEvent(t *testing.T) {
|
||||
responseChan: make(chan PolicyResponse, 100),
|
||||
}
|
||||
|
||||
// Create test event
|
||||
testEvent := createTestEvent("test-event-id", "test-pubkey", "test content", 1)
|
||||
// Generate real keypair for testing
|
||||
eventSigner, eventPubkey := generateTestKeypair(t)
|
||||
|
||||
// Create real test event with proper signing
|
||||
testEvent := createTestEvent(t, eventSigner, "test content", 1)
|
||||
|
||||
// Create policy event
|
||||
policyEvent := &PolicyEvent{
|
||||
E: testEvent,
|
||||
LoggedInPubkey: "test-logged-in-pubkey",
|
||||
LoggedInPubkey: hex.Enc(eventPubkey),
|
||||
IPAddress: "127.0.0.1",
|
||||
}
|
||||
|
||||
@@ -666,11 +782,14 @@ func TestPolicyManagerProcessEvent(t *testing.T) {
|
||||
func TestEdgeCasesEmptyPolicy(t *testing.T) {
|
||||
policy := &P{}
|
||||
|
||||
// Create test event
|
||||
testEvent := createTestEvent("test-event-id", "test-pubkey", "test content", 1)
|
||||
// Generate real keypair for testing
|
||||
eventSigner, eventPubkey := generateTestKeypair(t)
|
||||
|
||||
// Create real test event with proper signing
|
||||
testEvent := createTestEvent(t, eventSigner, "test content", 1)
|
||||
|
||||
// Should allow all events when policy is empty
|
||||
allowed, err := policy.CheckPolicy("write", testEvent, []byte("test-pubkey"), "127.0.0.1")
|
||||
allowed, err := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
@@ -712,11 +831,14 @@ func TestEdgeCasesLargeEvent(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
// Create test event with large content
|
||||
testEvent := createTestEvent("test-event-id", "test-pubkey", largeContent, 1)
|
||||
// Generate real keypair for testing
|
||||
eventSigner, eventPubkey := generateTestKeypair(t)
|
||||
|
||||
// Create real test event with large content
|
||||
testEvent := createTestEvent(t, eventSigner, largeContent, 1)
|
||||
|
||||
// Should block large event
|
||||
allowed, err := policy.CheckPolicy("write", testEvent, []byte("test-pubkey"), "127.0.0.1")
|
||||
allowed, err := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
@@ -805,6 +927,10 @@ func TestEdgeCasesManagerDoubleStart(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCheckGlobalRulePolicy(t *testing.T) {
|
||||
// Generate real keypairs for testing
|
||||
eventSigner, eventPubkey := generateTestKeypair(t)
|
||||
_, loggedInPubkey := generateTestKeypair(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
globalRule Rule
|
||||
@@ -815,19 +941,19 @@ func TestCheckGlobalRulePolicy(t *testing.T) {
|
||||
{
|
||||
name: "global rule with write allow - event allowed",
|
||||
globalRule: Rule{
|
||||
WriteAllow: []string{"746573742d7075626b6579"},
|
||||
WriteAllow: []string{hex.Enc(eventPubkey)},
|
||||
},
|
||||
event: createTestEvent("test-id", "test-pubkey", "test content", 1),
|
||||
loggedInPubkey: []byte("test-logged-in-pubkey"),
|
||||
event: createTestEvent(t, eventSigner, "test content", 1),
|
||||
loggedInPubkey: loggedInPubkey,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "global rule with write deny - event denied",
|
||||
globalRule: Rule{
|
||||
WriteDeny: []string{"746573742d7075626b6579"},
|
||||
WriteDeny: []string{hex.Enc(eventPubkey)},
|
||||
},
|
||||
event: createTestEvent("test-id", "test-pubkey", "test content", 1),
|
||||
loggedInPubkey: []byte("test-logged-in-pubkey"),
|
||||
event: createTestEvent(t, eventSigner, "test content", 1),
|
||||
loggedInPubkey: loggedInPubkey,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
@@ -835,8 +961,8 @@ func TestCheckGlobalRulePolicy(t *testing.T) {
|
||||
globalRule: Rule{
|
||||
SizeLimit: func() *int64 { v := int64(10); return &v }(),
|
||||
},
|
||||
event: createTestEvent("test-id", "test-pubkey", "this is a very long content that exceeds the size limit", 1),
|
||||
loggedInPubkey: []byte("test-logged-in-pubkey"),
|
||||
event: createTestEvent(t, eventSigner, "this is a very long content that exceeds the size limit", 1),
|
||||
loggedInPubkey: loggedInPubkey,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
@@ -845,11 +971,11 @@ func TestCheckGlobalRulePolicy(t *testing.T) {
|
||||
MaxAgeOfEvent: func() *int64 { v := int64(3600); return &v }(), // 1 hour
|
||||
},
|
||||
event: func() *event.E {
|
||||
ev := createTestEvent("test-id", "test-pubkey", "test content", 1)
|
||||
ev := createTestEvent(t, eventSigner, "test content", 1)
|
||||
ev.CreatedAt = time.Now().Unix() - 7200 // 2 hours ago
|
||||
return ev
|
||||
}(),
|
||||
loggedInPubkey: []byte("test-logged-in-pubkey"),
|
||||
loggedInPubkey: loggedInPubkey,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
@@ -858,11 +984,11 @@ func TestCheckGlobalRulePolicy(t *testing.T) {
|
||||
MaxAgeEventInFuture: func() *int64 { v := int64(3600); return &v }(), // 1 hour
|
||||
},
|
||||
event: func() *event.E {
|
||||
ev := createTestEvent("test-id", "test-pubkey", "test content", 1)
|
||||
ev := createTestEvent(t, eventSigner, "test content", 1)
|
||||
ev.CreatedAt = time.Now().Unix() + 7200 // 2 hours in future
|
||||
return ev
|
||||
}(),
|
||||
loggedInPubkey: []byte("test-logged-in-pubkey"),
|
||||
loggedInPubkey: loggedInPubkey,
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
@@ -882,23 +1008,26 @@ func TestCheckGlobalRulePolicy(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCheckPolicyWithGlobalRule(t *testing.T) {
|
||||
// Generate real keypairs for testing
|
||||
eventSigner, eventPubkey := generateTestKeypair(t)
|
||||
_, loggedInPubkey := generateTestKeypair(t)
|
||||
|
||||
// Test that global rule is applied first
|
||||
policy := &P{
|
||||
Global: Rule{
|
||||
WriteDeny: []string{"746573742d7075626b6579"}, // Deny test-pubkey globally
|
||||
WriteDeny: []string{hex.Enc(eventPubkey)}, // Deny event pubkey globally
|
||||
},
|
||||
Kind: Kinds{
|
||||
Whitelist: []int{1}, // Allow kind 1
|
||||
},
|
||||
Rules: map[int]Rule{
|
||||
1: {
|
||||
WriteAllow: []string{"746573742d7075626b6579"}, // Allow test-pubkey for kind 1
|
||||
WriteAllow: []string{hex.Enc(eventPubkey)}, // Allow event pubkey for kind 1
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
event := createTestEvent("test-id", "test-pubkey", "test content", 1)
|
||||
loggedInPubkey := []byte("test-logged-in-pubkey")
|
||||
event := createTestEvent(t, eventSigner, "test content", 1)
|
||||
|
||||
// Global rule should deny this event even though kind-specific rule would allow it
|
||||
allowed, err := policy.CheckPolicy("write", event, loggedInPubkey, "127.0.0.1")
|
||||
@@ -912,6 +1041,10 @@ func TestCheckPolicyWithGlobalRule(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMaxAgeChecks(t *testing.T) {
|
||||
// Generate real keypairs for testing
|
||||
eventSigner, _ := generateTestKeypair(t)
|
||||
_, loggedInPubkey := generateTestKeypair(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
rule Rule
|
||||
@@ -925,11 +1058,11 @@ func TestMaxAgeChecks(t *testing.T) {
|
||||
MaxAgeOfEvent: func() *int64 { v := int64(3600); return &v }(), // 1 hour
|
||||
},
|
||||
event: func() *event.E {
|
||||
ev := createTestEvent("test-id", "test-pubkey", "test content", 1)
|
||||
ev := createTestEvent(t, eventSigner, "test content", 1)
|
||||
ev.CreatedAt = time.Now().Unix() - 1800 // 30 minutes ago
|
||||
return ev
|
||||
}(),
|
||||
loggedInPubkey: []byte("test-logged-in-pubkey"),
|
||||
loggedInPubkey: loggedInPubkey,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
@@ -938,11 +1071,11 @@ func TestMaxAgeChecks(t *testing.T) {
|
||||
MaxAgeOfEvent: func() *int64 { v := int64(3600); return &v }(), // 1 hour
|
||||
},
|
||||
event: func() *event.E {
|
||||
ev := createTestEvent("test-id", "test-pubkey", "test content", 1)
|
||||
ev := createTestEvent(t, eventSigner, "test content", 1)
|
||||
ev.CreatedAt = time.Now().Unix() - 7200 // 2 hours ago
|
||||
return ev
|
||||
}(),
|
||||
loggedInPubkey: []byte("test-logged-in-pubkey"),
|
||||
loggedInPubkey: loggedInPubkey,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
@@ -951,11 +1084,11 @@ func TestMaxAgeChecks(t *testing.T) {
|
||||
MaxAgeEventInFuture: func() *int64 { v := int64(3600); return &v }(), // 1 hour
|
||||
},
|
||||
event: func() *event.E {
|
||||
ev := createTestEvent("test-id", "test-pubkey", "test content", 1)
|
||||
ev := createTestEvent(t, eventSigner, "test content", 1)
|
||||
ev.CreatedAt = time.Now().Unix() + 1800 // 30 minutes in future
|
||||
return ev
|
||||
}(),
|
||||
loggedInPubkey: []byte("test-logged-in-pubkey"),
|
||||
loggedInPubkey: loggedInPubkey,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
@@ -964,11 +1097,11 @@ func TestMaxAgeChecks(t *testing.T) {
|
||||
MaxAgeEventInFuture: func() *int64 { v := int64(3600); return &v }(), // 1 hour
|
||||
},
|
||||
event: func() *event.E {
|
||||
ev := createTestEvent("test-id", "test-pubkey", "test content", 1)
|
||||
ev := createTestEvent(t, eventSigner, "test content", 1)
|
||||
ev.CreatedAt = time.Now().Unix() + 7200 // 2 hours in future
|
||||
return ev
|
||||
}(),
|
||||
loggedInPubkey: []byte("test-logged-in-pubkey"),
|
||||
loggedInPubkey: loggedInPubkey,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
@@ -978,11 +1111,11 @@ func TestMaxAgeChecks(t *testing.T) {
|
||||
MaxAgeEventInFuture: func() *int64 { v := int64(1800); return &v }(), // 30 minutes
|
||||
},
|
||||
event: func() *event.E {
|
||||
ev := createTestEvent("test-id", "test-pubkey", "test content", 1)
|
||||
ev := createTestEvent(t, eventSigner, "test content", 1)
|
||||
ev.CreatedAt = time.Now().Unix() + 900 // 15 minutes in future
|
||||
return ev
|
||||
}(),
|
||||
loggedInPubkey: []byte("test-logged-in-pubkey"),
|
||||
loggedInPubkey: loggedInPubkey,
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
@@ -1004,6 +1137,9 @@ func TestMaxAgeChecks(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestScriptPolicyNotRunningFallsBackToDefault(t *testing.T) {
|
||||
// Generate real keypair for testing
|
||||
eventSigner, eventPubkey := generateTestKeypair(t)
|
||||
|
||||
// Create a policy with a script rule but no running manager, default policy is "allow"
|
||||
policy := &P{
|
||||
DefaultPolicy: "allow",
|
||||
@@ -1019,11 +1155,11 @@ func TestScriptPolicyNotRunningFallsBackToDefault(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
// Create test event
|
||||
testEvent := createTestEvent("test-event-id", "test-pubkey", "test content", 1)
|
||||
// Create real test event with proper signing
|
||||
testEvent := createTestEvent(t, eventSigner, "test content", 1)
|
||||
|
||||
// Should allow the event when script is configured but not running (falls back to default "allow")
|
||||
allowed, err := policy.CheckPolicy("write", testEvent, []byte("test-pubkey"), "127.0.0.1")
|
||||
allowed, err := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
@@ -1033,7 +1169,7 @@ func TestScriptPolicyNotRunningFallsBackToDefault(t *testing.T) {
|
||||
|
||||
// Test with default policy "deny"
|
||||
policy.DefaultPolicy = "deny"
|
||||
allowed2, err2 := policy.CheckPolicy("write", testEvent, []byte("test-pubkey"), "127.0.0.1")
|
||||
allowed2, err2 := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
||||
if err2 != nil {
|
||||
t.Errorf("Unexpected error: %v", err2)
|
||||
}
|
||||
@@ -1043,6 +1179,9 @@ func TestScriptPolicyNotRunningFallsBackToDefault(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDefaultPolicyAllow(t *testing.T) {
|
||||
// Generate real keypair for testing
|
||||
eventSigner, eventPubkey := generateTestKeypair(t)
|
||||
|
||||
// Test default policy "allow" behavior
|
||||
policy := &P{
|
||||
DefaultPolicy: "allow",
|
||||
@@ -1050,11 +1189,11 @@ func TestDefaultPolicyAllow(t *testing.T) {
|
||||
Rules: map[int]Rule{}, // No specific rules
|
||||
}
|
||||
|
||||
// Create test event for kind 1 (no specific rule exists)
|
||||
testEvent := createTestEvent("test-event-id", "test-pubkey", "test content", 1)
|
||||
// Create real test event with proper signing
|
||||
testEvent := createTestEvent(t, eventSigner, "test content", 1)
|
||||
|
||||
// Should allow the event with default policy "allow"
|
||||
allowed, err := policy.CheckPolicy("write", testEvent, []byte("test-pubkey"), "127.0.0.1")
|
||||
allowed, err := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
@@ -1064,6 +1203,9 @@ func TestDefaultPolicyAllow(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDefaultPolicyDeny(t *testing.T) {
|
||||
// Generate real keypair for testing
|
||||
eventSigner, eventPubkey := generateTestKeypair(t)
|
||||
|
||||
// Test default policy "deny" behavior
|
||||
policy := &P{
|
||||
DefaultPolicy: "deny",
|
||||
@@ -1071,11 +1213,11 @@ func TestDefaultPolicyDeny(t *testing.T) {
|
||||
Rules: map[int]Rule{}, // No specific rules
|
||||
}
|
||||
|
||||
// Create test event for kind 1 (no specific rule exists)
|
||||
testEvent := createTestEvent("test-event-id", "test-pubkey", "test content", 1)
|
||||
// Create real test event with proper signing
|
||||
testEvent := createTestEvent(t, eventSigner, "test content", 1)
|
||||
|
||||
// Should deny the event with default policy "deny"
|
||||
allowed, err := policy.CheckPolicy("write", testEvent, []byte("test-pubkey"), "127.0.0.1")
|
||||
allowed, err := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
@@ -1085,6 +1227,9 @@ func TestDefaultPolicyDeny(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDefaultPolicyEmpty(t *testing.T) {
|
||||
// Generate real keypair for testing
|
||||
eventSigner, eventPubkey := generateTestKeypair(t)
|
||||
|
||||
// Test empty default policy (should default to "allow")
|
||||
policy := &P{
|
||||
DefaultPolicy: "",
|
||||
@@ -1092,11 +1237,11 @@ func TestDefaultPolicyEmpty(t *testing.T) {
|
||||
Rules: map[int]Rule{}, // No specific rules
|
||||
}
|
||||
|
||||
// Create test event for kind 1 (no specific rule exists)
|
||||
testEvent := createTestEvent("test-event-id", "test-pubkey", "test content", 1)
|
||||
// Create real test event with proper signing
|
||||
testEvent := createTestEvent(t, eventSigner, "test content", 1)
|
||||
|
||||
// Should allow the event with empty default policy (defaults to "allow")
|
||||
allowed, err := policy.CheckPolicy("write", testEvent, []byte("test-pubkey"), "127.0.0.1")
|
||||
allowed, err := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
@@ -1106,6 +1251,9 @@ func TestDefaultPolicyEmpty(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDefaultPolicyInvalid(t *testing.T) {
|
||||
// Generate real keypair for testing
|
||||
eventSigner, eventPubkey := generateTestKeypair(t)
|
||||
|
||||
// Test invalid default policy (should default to "allow")
|
||||
policy := &P{
|
||||
DefaultPolicy: "invalid",
|
||||
@@ -1113,11 +1261,11 @@ func TestDefaultPolicyInvalid(t *testing.T) {
|
||||
Rules: map[int]Rule{}, // No specific rules
|
||||
}
|
||||
|
||||
// Create test event for kind 1 (no specific rule exists)
|
||||
testEvent := createTestEvent("test-event-id", "test-pubkey", "test content", 1)
|
||||
// Create real test event with proper signing
|
||||
testEvent := createTestEvent(t, eventSigner, "test content", 1)
|
||||
|
||||
// Should allow the event with invalid default policy (defaults to "allow")
|
||||
allowed, err := policy.CheckPolicy("write", testEvent, []byte("test-pubkey"), "127.0.0.1")
|
||||
allowed, err := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
@@ -1127,6 +1275,9 @@ func TestDefaultPolicyInvalid(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDefaultPolicyWithSpecificRule(t *testing.T) {
|
||||
// Generate real keypair for testing
|
||||
eventSigner, eventPubkey := generateTestKeypair(t)
|
||||
|
||||
// Test that specific rules override default policy
|
||||
policy := &P{
|
||||
DefaultPolicy: "deny", // Default is deny
|
||||
@@ -1139,11 +1290,11 @@ func TestDefaultPolicyWithSpecificRule(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
// Create test event for kind 1 (has specific rule)
|
||||
testEvent := createTestEvent("test-event-id", "test-pubkey", "test content", 1)
|
||||
// Create real test event with proper signing for kind 1 (has specific rule)
|
||||
testEvent := createTestEvent(t, eventSigner, "test content", 1)
|
||||
|
||||
// Should allow the event because specific rule allows it, despite default policy being "deny"
|
||||
allowed, err := policy.CheckPolicy("write", testEvent, []byte("test-pubkey"), "127.0.0.1")
|
||||
allowed, err := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
@@ -1151,11 +1302,11 @@ func TestDefaultPolicyWithSpecificRule(t *testing.T) {
|
||||
t.Error("Expected event to be allowed by specific rule, despite default_policy 'deny'")
|
||||
}
|
||||
|
||||
// Create test event for kind 2 (no specific rule exists)
|
||||
testEvent2 := createTestEvent("test-event-id-2", "test-pubkey", "test content", 2)
|
||||
// Create real test event with proper signing for kind 2 (no specific rule exists)
|
||||
testEvent2 := createTestEvent(t, eventSigner, "test content", 2)
|
||||
|
||||
// Should deny the event because no specific rule and default policy is "deny"
|
||||
allowed2, err2 := policy.CheckPolicy("write", testEvent2, []byte("test-pubkey"), "127.0.0.1")
|
||||
allowed2, err2 := policy.CheckPolicy("write", testEvent2, eventPubkey, "127.0.0.1")
|
||||
if err2 != nil {
|
||||
t.Errorf("Unexpected error: %v", err2)
|
||||
}
|
||||
@@ -1190,6 +1341,9 @@ func TestNewPolicyWithDefaultPolicyJSON(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestScriptProcessingFailureFallsBackToDefault(t *testing.T) {
|
||||
// Generate real keypair for testing
|
||||
eventSigner, eventPubkey := generateTestKeypair(t)
|
||||
|
||||
// Test that script processing failures fall back to default policy
|
||||
// We'll test this by using a manager that's not running (simulating failure)
|
||||
policy := &P{
|
||||
@@ -1206,11 +1360,11 @@ func TestScriptProcessingFailureFallsBackToDefault(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
// Create test event
|
||||
testEvent := createTestEvent("test-event-id", "test-pubkey", "test content", 1)
|
||||
// Create real test event with proper signing
|
||||
testEvent := createTestEvent(t, eventSigner, "test content", 1)
|
||||
|
||||
// Should allow the event when script is not running (falls back to default "allow")
|
||||
allowed, err := policy.checkScriptPolicy("write", testEvent, "policy.sh", []byte("test-pubkey"), "127.0.0.1")
|
||||
allowed, err := policy.checkScriptPolicy("write", testEvent, "policy.sh", eventPubkey, "127.0.0.1")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
@@ -1220,7 +1374,7 @@ func TestScriptProcessingFailureFallsBackToDefault(t *testing.T) {
|
||||
|
||||
// Test with default policy "deny"
|
||||
policy.DefaultPolicy = "deny"
|
||||
allowed2, err2 := policy.checkScriptPolicy("write", testEvent, "policy.sh", []byte("test-pubkey"), "127.0.0.1")
|
||||
allowed2, err2 := policy.checkScriptPolicy("write", testEvent, "policy.sh", eventPubkey, "127.0.0.1")
|
||||
if err2 != nil {
|
||||
t.Errorf("Unexpected error: %v", err2)
|
||||
}
|
||||
@@ -1230,6 +1384,11 @@ func TestScriptProcessingFailureFallsBackToDefault(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDefaultPolicyLogicWithRules(t *testing.T) {
|
||||
// Generate real keypairs for testing
|
||||
testSigner, _ := generateTestKeypair(t)
|
||||
deniedSigner, deniedPubkey := generateTestKeypair(t)
|
||||
_, loggedInPubkey := generateTestKeypair(t)
|
||||
|
||||
// Test that default policy logic works correctly with rules
|
||||
|
||||
// Test 1: default_policy "deny" - should only allow if rule explicitly allows
|
||||
@@ -1245,15 +1404,15 @@ func TestDefaultPolicyLogicWithRules(t *testing.T) {
|
||||
},
|
||||
2: {
|
||||
Description: "deny specific pubkey for kind 2",
|
||||
WriteDeny: []string{"64656e6965642d7075626b6579"}, // hex of "denied-pubkey"
|
||||
WriteDeny: []string{hex.Enc(deniedPubkey)},
|
||||
},
|
||||
// No rule for kind 3
|
||||
},
|
||||
}
|
||||
|
||||
// Kind 1: has rule that allows all - should be allowed
|
||||
event1 := createTestEvent("test-1", "test-pubkey", "content", 1)
|
||||
allowed1, err1 := policy1.CheckPolicy("write", event1, []byte("test-pubkey"), "127.0.0.1")
|
||||
event1 := createTestEvent(t, testSigner, "content", 1)
|
||||
allowed1, err1 := policy1.CheckPolicy("write", event1, loggedInPubkey, "127.0.0.1")
|
||||
if err1 != nil {
|
||||
t.Errorf("Unexpected error for kind 1: %v", err1)
|
||||
}
|
||||
@@ -1262,8 +1421,8 @@ func TestDefaultPolicyLogicWithRules(t *testing.T) {
|
||||
}
|
||||
|
||||
// Kind 2: has rule that denies specific pubkey - should be allowed for other pubkeys
|
||||
event2 := createTestEvent("test-2", "test-pubkey", "content", 2)
|
||||
allowed2, err2 := policy1.CheckPolicy("write", event2, []byte("test-pubkey"), "127.0.0.1")
|
||||
event2 := createTestEvent(t, testSigner, "content", 2)
|
||||
allowed2, err2 := policy1.CheckPolicy("write", event2, loggedInPubkey, "127.0.0.1")
|
||||
if err2 != nil {
|
||||
t.Errorf("Unexpected error for kind 2: %v", err2)
|
||||
}
|
||||
@@ -1272,8 +1431,8 @@ func TestDefaultPolicyLogicWithRules(t *testing.T) {
|
||||
}
|
||||
|
||||
// Kind 2: denied pubkey should be denied
|
||||
event2Denied := createTestEvent("test-2-denied", "denied-pubkey", "content", 2)
|
||||
allowed2Denied, err2Denied := policy1.CheckPolicy("write", event2Denied, []byte("test-pubkey"), "127.0.0.1")
|
||||
event2Denied := createTestEvent(t, deniedSigner, "content", 2)
|
||||
allowed2Denied, err2Denied := policy1.CheckPolicy("write", event2Denied, loggedInPubkey, "127.0.0.1")
|
||||
if err2Denied != nil {
|
||||
t.Errorf("Unexpected error for kind 2 denied: %v", err2Denied)
|
||||
}
|
||||
@@ -1282,8 +1441,8 @@ func TestDefaultPolicyLogicWithRules(t *testing.T) {
|
||||
}
|
||||
|
||||
// Kind 3: whitelisted but no rule - should follow default policy (deny)
|
||||
event3 := createTestEvent("test-3", "test-pubkey", "content", 3)
|
||||
allowed3, err3 := policy1.CheckPolicy("write", event3, []byte("test-pubkey"), "127.0.0.1")
|
||||
event3 := createTestEvent(t, testSigner, "content", 3)
|
||||
allowed3, err3 := policy1.CheckPolicy("write", event3, loggedInPubkey, "127.0.0.1")
|
||||
if err3 != nil {
|
||||
t.Errorf("Unexpected error for kind 3: %v", err3)
|
||||
}
|
||||
@@ -1300,15 +1459,15 @@ func TestDefaultPolicyLogicWithRules(t *testing.T) {
|
||||
Rules: map[int]Rule{
|
||||
1: {
|
||||
Description: "deny specific pubkey for kind 1",
|
||||
WriteDeny: []string{"64656e6965642d7075626b6579"}, // hex of "denied-pubkey"
|
||||
WriteDeny: []string{hex.Enc(deniedPubkey)},
|
||||
},
|
||||
// No rules for kind 2, 3
|
||||
},
|
||||
}
|
||||
|
||||
// Kind 1: has rule that denies specific pubkey - should be allowed for other pubkeys
|
||||
event1Allow := createTestEvent("test-1-allow", "test-pubkey", "content", 1)
|
||||
allowed1Allow, err1Allow := policy2.CheckPolicy("write", event1Allow, []byte("test-pubkey"), "127.0.0.1")
|
||||
event1Allow := createTestEvent(t, testSigner, "content", 1)
|
||||
allowed1Allow, err1Allow := policy2.CheckPolicy("write", event1Allow, loggedInPubkey, "127.0.0.1")
|
||||
if err1Allow != nil {
|
||||
t.Errorf("Unexpected error for kind 1 allow: %v", err1Allow)
|
||||
}
|
||||
@@ -1317,8 +1476,8 @@ func TestDefaultPolicyLogicWithRules(t *testing.T) {
|
||||
}
|
||||
|
||||
// Kind 1: denied pubkey should be denied
|
||||
event1Deny := createTestEvent("test-1-deny", "denied-pubkey", "content", 1)
|
||||
allowed1Deny, err1Deny := policy2.CheckPolicy("write", event1Deny, []byte("test-pubkey"), "127.0.0.1")
|
||||
event1Deny := createTestEvent(t, deniedSigner, "content", 1)
|
||||
allowed1Deny, err1Deny := policy2.CheckPolicy("write", event1Deny, loggedInPubkey, "127.0.0.1")
|
||||
if err1Deny != nil {
|
||||
t.Errorf("Unexpected error for kind 1 deny: %v", err1Deny)
|
||||
}
|
||||
@@ -1327,8 +1486,8 @@ func TestDefaultPolicyLogicWithRules(t *testing.T) {
|
||||
}
|
||||
|
||||
// Kind 2: whitelisted but no rule - should follow default policy (allow)
|
||||
event2Allow := createTestEvent("test-2-allow", "test-pubkey", "content", 2)
|
||||
allowed2Allow, err2Allow := policy2.CheckPolicy("write", event2Allow, []byte("test-pubkey"), "127.0.0.1")
|
||||
event2Allow := createTestEvent(t, testSigner, "content", 2)
|
||||
allowed2Allow, err2Allow := policy2.CheckPolicy("write", event2Allow, loggedInPubkey, "127.0.0.1")
|
||||
if err2Allow != nil {
|
||||
t.Errorf("Unexpected error for kind 2 allow: %v", err2Allow)
|
||||
}
|
||||
|
||||
@@ -307,7 +307,7 @@ func (r *Client) ConnectWithTLS(
|
||||
if r.notices != nil {
|
||||
r.notices <- env.Message
|
||||
} else {
|
||||
log.E.F("NOTICE from %s: '%s'\n", r.URL, env.Message)
|
||||
log.E.F("NOTICE from %s: '%s'", r.URL, env.Message)
|
||||
}
|
||||
case authenvelope.L:
|
||||
env := authenvelope.NewChallenge()
|
||||
|
||||
@@ -3,21 +3,19 @@ package ws
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"lol.mleku.dev/errorf"
|
||||
"next.orly.dev/pkg/utils/units"
|
||||
|
||||
ws "github.com/coder/websocket"
|
||||
)
|
||||
|
||||
// Connection represents a websocket connection to a Nostr relay.
|
||||
type Connection struct {
|
||||
conn *ws.Conn
|
||||
conn *websocket.Conn
|
||||
}
|
||||
|
||||
// NewConnection creates a new websocket connection to a Nostr relay.
|
||||
@@ -25,10 +23,23 @@ func NewConnection(
|
||||
ctx context.Context, url string, reqHeader http.Header,
|
||||
tlsConfig *tls.Config,
|
||||
) (c *Connection, err error) {
|
||||
var conn *ws.Conn
|
||||
if conn, _, err = ws.Dial(
|
||||
ctx, url, getConnectionOptions(reqHeader, tlsConfig),
|
||||
); err != nil {
|
||||
var conn *websocket.Conn
|
||||
var resp *http.Response
|
||||
dialer := getConnectionOptions(reqHeader, tlsConfig)
|
||||
|
||||
// Prepare headers with default User-Agent if not present
|
||||
headers := reqHeader
|
||||
if headers == nil {
|
||||
headers = make(http.Header)
|
||||
}
|
||||
if headers.Get("User-Agent") == "" {
|
||||
headers.Set("User-Agent", "github.com/nbd-wtf/go-nostr")
|
||||
}
|
||||
|
||||
if conn, resp, err = dialer.DialContext(ctx, url, headers); err != nil {
|
||||
if resp != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
conn.SetReadLimit(33 * units.Mb)
|
||||
@@ -41,7 +52,14 @@ func NewConnection(
|
||||
func (c *Connection) WriteMessage(
|
||||
ctx context.Context, data []byte,
|
||||
) (err error) {
|
||||
if err = c.conn.Write(ctx, ws.MessageText, data); err != nil {
|
||||
deadline := time.Now().Add(10 * time.Second)
|
||||
if ctx != nil {
|
||||
if d, ok := ctx.Deadline(); ok {
|
||||
deadline = d
|
||||
}
|
||||
}
|
||||
c.conn.SetWriteDeadline(deadline)
|
||||
if err = c.conn.WriteMessage(websocket.TextMessage, data); err != nil {
|
||||
err = errorf.E("failed to write message: %w", err)
|
||||
return
|
||||
}
|
||||
@@ -52,11 +70,22 @@ func (c *Connection) WriteMessage(
|
||||
func (c *Connection) ReadMessage(
|
||||
ctx context.Context, buf io.Writer,
|
||||
) (err error) {
|
||||
var reader io.Reader
|
||||
if _, reader, err = c.conn.Reader(ctx); err != nil {
|
||||
deadline := time.Now().Add(60 * time.Second)
|
||||
if ctx != nil {
|
||||
if d, ok := ctx.Deadline(); ok {
|
||||
deadline = d
|
||||
}
|
||||
}
|
||||
c.conn.SetReadDeadline(deadline)
|
||||
messageType, reader, err := c.conn.NextReader()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to get reader: %w", err)
|
||||
return
|
||||
}
|
||||
if messageType != websocket.TextMessage && messageType != websocket.BinaryMessage {
|
||||
err = fmt.Errorf("unexpected message type: %d", messageType)
|
||||
return
|
||||
}
|
||||
if _, err = io.Copy(buf, reader); err != nil {
|
||||
err = fmt.Errorf("failed to read message: %w", err)
|
||||
return
|
||||
@@ -66,14 +95,18 @@ func (c *Connection) ReadMessage(
|
||||
|
||||
// Close closes the websocket connection.
|
||||
func (c *Connection) Close() error {
|
||||
return c.conn.Close(ws.StatusNormalClosure, "")
|
||||
c.conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Now().Add(time.Second))
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
// Ping sends a ping message to the websocket connection.
|
||||
func (c *Connection) Ping(ctx context.Context) error {
|
||||
ctx, cancel := context.WithTimeoutCause(
|
||||
ctx, time.Millisecond*800, errors.New("ping took too long"),
|
||||
)
|
||||
defer cancel()
|
||||
return c.conn.Ping(ctx)
|
||||
deadline := time.Now().Add(800 * time.Millisecond)
|
||||
if ctx != nil {
|
||||
if d, ok := ctx.Deadline(); ok {
|
||||
deadline = d
|
||||
}
|
||||
}
|
||||
c.conn.SetWriteDeadline(deadline)
|
||||
return c.conn.WriteControl(websocket.PingMessage, []byte{}, deadline)
|
||||
}
|
||||
|
||||
@@ -5,32 +5,21 @@ package ws
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"time"
|
||||
|
||||
ws "github.com/coder/websocket"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
var defaultConnectionOptions = &ws.DialOptions{
|
||||
CompressionMode: ws.CompressionContextTakeover,
|
||||
HTTPHeader: http.Header{
|
||||
textproto.CanonicalMIMEHeaderKey("User-Agent"): {"github.com/nbd-wtf/go-nostr"},
|
||||
},
|
||||
}
|
||||
|
||||
func getConnectionOptions(
|
||||
requestHeader http.Header, tlsConfig *tls.Config,
|
||||
) *ws.DialOptions {
|
||||
if requestHeader == nil && tlsConfig == nil {
|
||||
return defaultConnectionOptions
|
||||
}
|
||||
|
||||
return &ws.DialOptions{
|
||||
HTTPHeader: requestHeader,
|
||||
CompressionMode: ws.CompressionContextTakeover,
|
||||
HTTPClient: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
},
|
||||
},
|
||||
) *websocket.Dialer {
|
||||
dialer := &websocket.Dialer{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
TLSClientConfig: tlsConfig,
|
||||
HandshakeTimeout: 10 * time.Second,
|
||||
}
|
||||
// Headers are passed directly to DialContext, not set on Dialer
|
||||
// The User-Agent header will be set when calling DialContext if not present
|
||||
return dialer
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package spider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -12,6 +11,7 @@ import (
|
||||
"lol.mleku.dev/log"
|
||||
"next.orly.dev/pkg/database"
|
||||
"next.orly.dev/pkg/encoders/filter"
|
||||
"next.orly.dev/pkg/encoders/hex"
|
||||
"next.orly.dev/pkg/encoders/tag"
|
||||
"next.orly.dev/pkg/encoders/timestamp"
|
||||
"next.orly.dev/pkg/interfaces/publisher"
|
||||
@@ -414,17 +414,20 @@ func (rc *RelayConnection) createBatchSubscription(batchID string, pubkeys [][]b
|
||||
}
|
||||
|
||||
// Create filters: one for authors, one for p tags
|
||||
var pTags tag.S
|
||||
// For #p tag filters, all pubkeys must be in a single tag array as hex-encoded strings
|
||||
tagElements := [][]byte{[]byte("p")} // First element is the key
|
||||
for _, pk := range pubkeys {
|
||||
pTags = append(pTags, tag.NewFromAny("p", pk))
|
||||
pkHex := hex.EncAppend(nil, pk)
|
||||
tagElements = append(tagElements, pkHex)
|
||||
}
|
||||
pTags := &tag.S{tag.NewFromBytesSlice(tagElements...)}
|
||||
|
||||
filters := filter.NewS(
|
||||
&filter.F{
|
||||
Authors: tag.NewFromBytesSlice(pubkeys...),
|
||||
},
|
||||
&filter.F{
|
||||
Tags: tag.NewS(pTags...),
|
||||
Tags: pTags,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -465,10 +468,6 @@ func (bs *BatchSubscription) handleEvents() {
|
||||
|
||||
// Save event to database
|
||||
if _, err := bs.relay.spider.db.SaveEvent(bs.relay.ctx, ev); err != nil {
|
||||
if !chk.E(err) {
|
||||
log.T.F("spider: saved event %s from %s",
|
||||
hex.EncodeToString(ev.ID[:]), bs.relay.url)
|
||||
}
|
||||
} else {
|
||||
// Publish event if it was newly saved
|
||||
if bs.relay.spider.pub != nil {
|
||||
@@ -527,10 +526,14 @@ func (rc *RelayConnection) performCatchup(sub *BatchSubscription, disconnectTime
|
||||
sinceTs := timestamp.T{V: since.Unix()}
|
||||
untilTs := timestamp.T{V: until.Unix()}
|
||||
|
||||
var pTags tag.S
|
||||
// Create filters with hex-encoded pubkeys for #p tags
|
||||
// All pubkeys must be in a single tag array
|
||||
tagElements := [][]byte{[]byte("p")} // First element is the key
|
||||
for _, pk := range sub.pubkeys {
|
||||
pTags = append(pTags, tag.NewFromAny("p", pk))
|
||||
pkHex := hex.EncAppend(nil, pk)
|
||||
tagElements = append(tagElements, pkHex)
|
||||
}
|
||||
pTags := &tag.S{tag.NewFromBytesSlice(tagElements...)}
|
||||
|
||||
filters := filter.NewS(
|
||||
&filter.F{
|
||||
@@ -539,7 +542,7 @@ func (rc *RelayConnection) performCatchup(sub *BatchSubscription, disconnectTime
|
||||
Until: &untilTs,
|
||||
},
|
||||
&filter.F{
|
||||
Tags: tag.NewS(pTags...),
|
||||
Tags: pTags,
|
||||
Since: &sinceTs,
|
||||
Until: &untilTs,
|
||||
},
|
||||
@@ -582,7 +585,7 @@ func (rc *RelayConnection) performCatchup(sub *BatchSubscription, disconnectTime
|
||||
if _, err := rc.spider.db.SaveEvent(rc.ctx, ev); err != nil {
|
||||
if !chk.E(err) {
|
||||
log.T.F("spider: catch-up saved event %s from %s",
|
||||
hex.EncodeToString(ev.ID[:]), rc.url)
|
||||
hex.Enc(ev.ID[:]), rc.url)
|
||||
}
|
||||
} else {
|
||||
// Publish event if it was newly saved
|
||||
|
||||
@@ -1 +1 @@
|
||||
v0.19.9
|
||||
v0.20.0
|
||||
Reference in New Issue
Block a user