diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 2f9bc94..3b0525e 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -22,4 +22,4 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: - version: v1.60 \ No newline at end of file + version: v1.63.4 \ No newline at end of file diff --git a/delivery/websocket/event_handler.go b/delivery/websocket/event_handler.go index 8b999e1..4934783 100644 --- a/delivery/websocket/event_handler.go +++ b/delivery/websocket/event_handler.go @@ -1,10 +1,10 @@ package websocket import ( - "context" "fmt" "slices" "strconv" + "strings" "time" "github.com/dezh-tech/immortal/infrastructure/redis" @@ -14,7 +14,6 @@ import ( "github.com/dezh-tech/immortal/types/filter" "github.com/dezh-tech/immortal/types/message" "github.com/gorilla/websocket" - gredis "github.com/redis/go-redis/v9" ) // handleEvent handles new incoming EVENT messages from client. @@ -56,80 +55,18 @@ func (s *Server) handleEvent(conn *websocket.Conn, m message.Message) { //nolint return } - qCtx, cancel := context.WithTimeout(context.Background(), s.redis.QueryTimeout) - defer cancel() - - pipe := s.redis.Client.Pipeline() - - bloomCheckCmd := pipe.BFExists(qCtx, s.redis.BloomFilterName, eID[:]) - - var whiteListCheckCmd *gredis.BoolCmd - - if s.config.Limitation.RestrictedWrites { - whiteListCheckCmd = pipe.CFExists(qCtx, s.redis.WhiteListFilterName, msg.Event.PublicKey) - } - - blackListCheckCmd := pipe.CFExists(qCtx, s.redis.BlackListFilterName, msg.Event.PublicKey) - - _, err := pipe.Exec(qCtx) - if err != nil { - logger.Error("checking bloom filter", "err", err.Error()) - } - - exists, err := bloomCheckCmd.Result() - if err != nil { - okm := message.MakeOK(false, msg.Event.ID, "error: internal error") - _ = conn.WriteMessage(1, okm) - - status = serverFail - - return - } - - if exists { - okm := message.MakeOK(false, msg.Event.ID, "duplicate: this event is already received.") - _ = conn.WriteMessage(1, okm) - - return - } - - isBlackListed, err := blackListCheckCmd.Result() - if err != nil { - okm := message.MakeOK(false, msg.Event.ID, "error: internal error") - _ = conn.WriteMessage(1, okm) - - status = serverFail - - return - } - if isBlackListed { - okm := message.MakeOK(false, msg.Event.ID, "blocked: pubkey is blocked, contact support for more details.") + if err := s.redis.CheckAcceptability(s.config.Limitation.RestrictedWrites, + eID[:], msg.Event.PublicKey); err != nil { + okm := message.MakeOK(false, msg.Event.ID, err.Error()) _ = conn.WriteMessage(1, okm) status = limitsFail - return - } - - if s.config.Limitation.RestrictedWrites { - isWhiteListed, err := whiteListCheckCmd.Result() - if err != nil { - okm := message.MakeOK(false, msg.Event.ID, "error: internal error") - _ = conn.WriteMessage(1, okm) - + if strings.HasPrefix(err.Error(), "internal") { status = serverFail - - return } - if !isWhiteListed { - okm := message.MakeOK(false, msg.Event.ID, "restricted: not allowed to write.") - _ = conn.WriteMessage(1, okm) - - status = limitsFail - - return - } + return } client, ok := s.conns[conn] @@ -192,9 +129,9 @@ func (s *Server) handleEvent(conn *websocket.Conn, m message.Message) { //nolint } if msg.Event.Kind == types.KindRightToVanish { - relays := msg.Event.Tags.GetValues("relays") - if !(slices.Contains(relays, "ALL_RELAYS") || - slices.Contains(relays, s.config.URL.String())) { + relays := msg.Event.Tags.GetValues("relay") + if !slices.Contains(relays, "ALL_RELAYS") && + !slices.Contains(relays, s.config.URL.String()) { okm := message.MakeOK(false, msg.Event.ID, "error: can't execute vanish request.", @@ -254,8 +191,7 @@ func (s *Server) handleEvent(conn *websocket.Conn, m message.Message) { //nolint _ = conn.WriteMessage(1, message.MakeOK(true, msg.Event.ID, "")) - _, err = s.redis.Client.BFAdd(qCtx, s.redis.BloomFilterName, eID[:]).Result() - if err != nil { + if err := s.redis.AddEventToBloom(eID[:]); err != nil { logger.Info("adding event to bloom filter", "err", err.Error(), msg.Event.ID) } diff --git a/go.mod b/go.mod index 7e9ded4..7079255 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,6 @@ require ( github.com/stretchr/testify v1.10.0 github.com/tidwall/gjson v1.18.0 go.mongodb.org/mongo-driver v1.17.2 - golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 google.golang.org/grpc v1.69.2 google.golang.org/protobuf v1.36.2 gopkg.in/natefinch/lumberjack.v2 v2.2.1 diff --git a/go.sum b/go.sum index 15073b2..38b2921 100644 --- a/go.sum +++ b/go.sum @@ -109,8 +109,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= diff --git a/infrastructure/redis/redis.go b/infrastructure/redis/redis.go index cba9bcc..783d43d 100644 --- a/infrastructure/redis/redis.go +++ b/infrastructure/redis/redis.go @@ -2,6 +2,7 @@ package redis import ( "context" + "errors" "fmt" "time" @@ -52,6 +53,71 @@ func (r *Redis) Close() error { return r.Client.Close() } +func (r *Redis) AddEventToBloom(id []byte) error { + ctx, cancel := context.WithTimeout(context.Background(), r.QueryTimeout) + defer cancel() + + _, err := r.Client.BFAdd(ctx, r.BloomFilterName, id).Result() + if err != nil { + return err + } + + return nil +} + +func (r *Redis) CheckAcceptability(restrictedWrites bool, eid []byte, pubkey string) error { + ctx, cancel := context.WithTimeout(context.Background(), r.QueryTimeout) + defer cancel() + + pipe := r.Client.Pipeline() + + bloomCheckCmd := pipe.BFExists(ctx, r.BloomFilterName, eid) + + var whiteListCheckCmd *redis.BoolCmd + + if restrictedWrites { + whiteListCheckCmd = pipe.CFExists(ctx, r.WhiteListFilterName, pubkey) + } + + blackListCheckCmd := pipe.CFExists(ctx, r.BlackListFilterName, pubkey) + + _, err := pipe.Exec(ctx) + if err != nil { + return errors.New("internal: error while checking event acceptability") + } + + exists, err := bloomCheckCmd.Result() + if err != nil { + return errors.New("internal: cna't lookup bloom filter") + } + + if exists { + return errors.New("duplicate: this event is already received") + } + + isBlackListed, err := blackListCheckCmd.Result() + if err != nil { + return errors.New("internal: cna't lookup black list") + } + + if isBlackListed { + return errors.New("blocked: pubkey is blocked") + } + + if restrictedWrites { + isWhiteListed, err := whiteListCheckCmd.Result() + if err != nil { + return errors.New("internal: cna't lookup white list") + } + + if !isWhiteListed { + return errors.New("restricted: not allowed to write") + } + } + + return nil +} + // ! note: delayed tasks probably are not concurrent safe at the moment. func (r *Redis) AddDelayedTask(listName string, data string, delay time.Duration, diff --git a/pkg/utils/random.go b/pkg/utils/random.go index 7b052f4..53e145d 100644 --- a/pkg/utils/random.go +++ b/pkg/utils/random.go @@ -1,9 +1,8 @@ package utils import ( - "time" - - "golang.org/x/exp/rand" + "crypto/rand" + "math/big" ) const ( @@ -14,19 +13,19 @@ const ( ) func GenerateChallenge(n int) string { - src := rand.NewSource(uint64(time.Now().UnixNano())) - b := make([]byte, n) - for i, cache, remain := n-1, src.Uint64(), letterIdxMax; i >= 0; { - if remain == 0 { - cache, remain = src.Uint64(), letterIdxMax - } - if idx := cache & letterIdxMask; idx < uint64(len(chars)) { - b[i] = chars[idx] - i-- - } - cache >>= letterIdxBits - remain-- + token := "" + for i := 0; i < n; i++ { + token += string(chars[cryptoRandSecure(int64(len(chars)))]) + } + + return token +} + +func cryptoRandSecure(n int64) int64 { + nBig, err := rand.Int(rand.Reader, big.NewInt(n)) + if err != nil { + return 0 } - return string(b) + return nBig.Int64() } diff --git a/types/kind.go b/types/kind.go index 9ae1320..30dc38f 100644 --- a/types/kind.go +++ b/types/kind.go @@ -254,4 +254,5 @@ var KindToName = map[Kind]string{ KindVideoViewEvent: "video_view_events", KindCommunityDefinition: "community_definitions", KindGroupsMetadata: "groups_metadata", + KindRightToVanish: "right_to_vanish", }