Add an RPC endpoint listing all members
Currently an RPC endpoint which lists only the connected members is available. A list of all members, even those who are offline, is available only using the web dashboard. This pull request adds an RPC endpoint which lists all members of the room. This new endpoint can be used to augment SSB clients with extra information about the rooms. For example friends who are room members can be displayed in room-related settings or information about shared rooms can be displayed in user profiles. The new endpoint is a source endpoint called ["room", "members"]. Source endpoint was selected to make it possible to return multiple smaller responses instead of one large response - an async endpoint could struggle to return the list of all members in case of larger rooms. Each response carries a list of member objects. Currently the implementation naively returns one member per response message but that can be adjusted in the future. Currently the request takes no arguments but extra options could be added in the future. Currently member objects only have one property: their id. This can be extended in the future.
This commit is contained in:
parent
158e5129ba
commit
ea9b22cfa7
|
@ -43,7 +43,7 @@ func (h *Handler) attendants(ctx context.Context, req *muxrpc.Request, snk *muxr
|
|||
}
|
||||
|
||||
if pm == roomdb.ModeCommunity || pm == roomdb.ModeRestricted {
|
||||
_, err := h.members.GetByFeed(ctx, *peer)
|
||||
_, err := h.membersdb.GetByFeed(ctx, *peer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("external user are not allowed to enumerate members")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
// SPDX-FileCopyrightText: 2021 The NGI Pointer Secure-Scuttlebutt Team of 2020/2021
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/v2/internal/network"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/v2/roomdb"
|
||||
"go.cryptoscope.co/muxrpc/v2"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
)
|
||||
|
||||
type Member struct {
|
||||
ID refs.FeedRef `json:"id"`
|
||||
}
|
||||
|
||||
func (h *Handler) members(ctx context.Context, req *muxrpc.Request, snk *muxrpc.ByteSink) error {
|
||||
peer, err := network.GetFeedRefFromAddr(req.RemoteAddr())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pm, err := h.config.GetPrivacyMode(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("running with unknown privacy mode: %w", err)
|
||||
}
|
||||
|
||||
if pm == roomdb.ModeCommunity || pm == roomdb.ModeRestricted {
|
||||
_, err := h.membersdb.GetByFeed(ctx, *peer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("external user are not allowed to list members: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
members, err := h.membersdb.List(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing members: %w", err)
|
||||
}
|
||||
|
||||
snk.SetEncoding(muxrpc.TypeJSON)
|
||||
|
||||
for _, member := range members {
|
||||
if err = json.NewEncoder(snk).Encode([]Member{
|
||||
{
|
||||
ID: member.PubKey,
|
||||
},
|
||||
}); err != nil {
|
||||
return fmt.Errorf("encoder error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return snk.Close()
|
||||
}
|
|
@ -30,7 +30,7 @@ func New(log kitlog.Logger, netInfo network.ServerEndpointDetails, m *roomstate.
|
|||
h.netInfo = netInfo
|
||||
h.logger = log
|
||||
h.state = m
|
||||
h.members = members
|
||||
h.membersdb = members
|
||||
h.config = config
|
||||
|
||||
return h
|
||||
|
@ -59,6 +59,7 @@ func (h *Handler) RegisterRoom(mux typemux.HandlerMux) {
|
|||
mux.RegisterAsync(append(namespace, "ping"), typemux.AsyncFunc(h.ping))
|
||||
|
||||
mux.RegisterSource(append(namespace, "attendants"), typemux.SourceFunc(h.attendants))
|
||||
mux.RegisterSource(append(namespace, "members"), typemux.SourceFunc(h.members))
|
||||
|
||||
mux.RegisterDuplex(append(namespace, "connect"), connectHandler{
|
||||
logger: h.logger,
|
||||
|
|
|
@ -23,10 +23,10 @@ import (
|
|||
type Handler struct {
|
||||
logger kitlog.Logger
|
||||
|
||||
netInfo network.ServerEndpointDetails
|
||||
state *roomstate.Manager
|
||||
members roomdb.MembersService
|
||||
config roomdb.RoomConfig
|
||||
netInfo network.ServerEndpointDetails
|
||||
state *roomstate.Manager
|
||||
membersdb roomdb.MembersService
|
||||
config roomdb.RoomConfig
|
||||
}
|
||||
|
||||
type MetadataReply struct {
|
||||
|
@ -50,7 +50,7 @@ func (h *Handler) metadata(ctx context.Context, req *muxrpc.Request) (interface{
|
|||
reply.Name = h.netInfo.Domain
|
||||
|
||||
// check if caller is a member
|
||||
if _, err := h.members.GetByFeed(ctx, *ref); err != nil {
|
||||
if _, err := h.membersdb.GetByFeed(ctx, *ref); err != nil {
|
||||
if !errors.Is(err, roomdb.ErrNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ func (h *Handler) endpoints(ctx context.Context, req *muxrpc.Request, snk *muxrp
|
|||
case roomdb.ModeCommunity:
|
||||
fallthrough
|
||||
case roomdb.ModeRestricted:
|
||||
_, err := h.members.GetByFeed(ctx, *peer)
|
||||
_, err := h.membersdb.GetByFeed(ctx, *peer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("external user are not allowed to enumerate members")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
// SPDX-FileCopyrightText: 2021 The NGI Pointer Secure-Scuttlebutt Team of 2020/2021
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package go_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/v2/internal/maybemod/keys"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/v2/muxrpc/handlers/tunnel/server"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/v2/roomdb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/v2/roomsrv"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.cryptoscope.co/muxrpc/v2"
|
||||
)
|
||||
|
||||
// this tests the new room.members call
|
||||
func TestRoomMembers(t *testing.T) {
|
||||
testInit(t)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
r := require.New(t)
|
||||
|
||||
appKey := make([]byte, 32)
|
||||
rand.Read(appKey)
|
||||
|
||||
netOpts := []roomsrv.Option{
|
||||
roomsrv.WithAppKey(appKey),
|
||||
roomsrv.WithContext(ctx),
|
||||
}
|
||||
|
||||
session := makeNamedTestBot(t, "srv", ctx, netOpts)
|
||||
|
||||
aliceKey, err := keys.NewKeyPair(nil)
|
||||
r.NoError(err)
|
||||
|
||||
bobKey, err := keys.NewKeyPair(nil)
|
||||
r.NoError(err)
|
||||
|
||||
bobSession := makeNamedTestBot(t, "bob", ctx, append(netOpts,
|
||||
roomsrv.WithKeyPair(bobKey),
|
||||
))
|
||||
|
||||
_, err = session.srv.Members.Add(ctx, aliceKey.Feed, roomdb.RoleMember)
|
||||
r.NoError(err)
|
||||
|
||||
_, err = session.srv.Members.Add(ctx, bobKey.Feed, roomdb.RoleMember)
|
||||
r.NoError(err)
|
||||
|
||||
// allow bots to dial the remote
|
||||
// side-effect of re-using a room-server as the client
|
||||
_, err = bobSession.srv.Members.Add(ctx, session.srv.Whoami(), roomdb.RoleMember)
|
||||
r.NoError(err)
|
||||
|
||||
err = bobSession.srv.Network.Connect(ctx, session.srv.Network.GetListenAddr())
|
||||
r.NoError(err, "connect A to the Server")
|
||||
|
||||
t.Log("letting handshaking settle..")
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
clientForServer, ok := bobSession.srv.Network.GetEndpointFor(session.srv.Whoami())
|
||||
r.True(ok)
|
||||
|
||||
src, err := clientForServer.Source(ctx, muxrpc.TypeString, muxrpc.Method{"room", "members"})
|
||||
r.NoError(err)
|
||||
|
||||
var responses []server.Member
|
||||
for src.Next(ctx) {
|
||||
bytes, err := src.Bytes()
|
||||
r.NoError(err)
|
||||
|
||||
var members []server.Member
|
||||
err = json.Unmarshal(bytes, &members)
|
||||
r.NoError(err)
|
||||
responses = append(responses, members...)
|
||||
}
|
||||
|
||||
r.Equal(
|
||||
[]server.Member{
|
||||
{
|
||||
ID: aliceKey.Feed,
|
||||
},
|
||||
{
|
||||
ID: bobKey.Feed,
|
||||
},
|
||||
},
|
||||
responses,
|
||||
)
|
||||
}
|
|
@ -45,6 +45,7 @@ const manifest manifestHandler = `
|
|||
|
||||
"connect": "duplex",
|
||||
"attendants": "source",
|
||||
"members": "source",
|
||||
"metadata": "async",
|
||||
"ping": "sync"
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue