From cc41eae0e90f0cb1d0a13f425d007cc5103e65fd Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Thu, 22 Sep 2022 02:55:12 +0000 Subject: [PATCH 1/9] Fix SSB-NGI links in README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ac0ef1d2..2d628027 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,9 @@ It includes: * Rooms v1 (`tunnel.connect`, `tunnel.endpoints`, etc.) * User management (allow- & denylisting + moderator & administrator roles), all administered via the web dashboard -* Multiple [privacy modes](https://ssb-ngi-pointer.github.io/rooms2/#privacy-modes) -* [Sign-in with SSB](https://ssb-ngi-pointer.github.io/ssb-http-auth-spec/) -* [HTTP Invites](https://github.com/ssb-ngi-pointer/ssb-http-invite-spec) +* Multiple [privacy modes](https://ssbc.github.io/rooms2/#privacy-modes) +* [Sign-in with SSB](https://ssbc.github.io/ssb-http-auth-spec/) +* [HTTP Invites](https://github.com/ssbc/ssb-http-invite-spec) * Alias management For a comprehensive introduction to rooms 2.0, 🎥 [watch this video](https://www.youtube.com/watch?v=W5p0y_MWwDE). From 26b8baefa01b46ab1c52e1164a4b7f8a9907d63f Mon Sep 17 00:00:00 2001 From: Kyle Maas Date: Fri, 21 Oct 2022 13:57:48 -0400 Subject: [PATCH 2/9] Couple of documentation changes for Debian package --- docs/deployment.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/deployment.md b/docs/deployment.md index c8d34748..7eb638d8 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -113,6 +113,13 @@ When the process is complete with `certbot`, pay attention to where the certific in the filesystem. If it's at `/etc/letsencrypt/live/hermies.club`, it's correct, otherwise you may need to rename it e.g. `hermies.club-0001` to `hermies.club`. +The example nginx configuration uses prebuilt Diffie-Hellman parameters. You can generate these +with the following command: + +``` +openssl dhparam -out /etc/letsencrypt/ssl-dhparams.pem 2048 +``` + Then restart your server, e.g. `systemctl restart nginx`. If you see such errors as the following when setting up your deployment: @@ -164,5 +171,11 @@ example (with custom repo location, only needed if you setup your with a custom ./insert-user -repo "/ssb-go-room-secrets" "@Bp5Z5TQKv6E/Y+QZn/3LiDWMPi63EP8MHsXZ4tiIb2w=.ed25519" ``` +Or if you installed go-ssb-room using the Debian package: + +``` +sudo ./insert-user -repo "/var/lib/go-ssb-room" "@Bp5Z5TQKv6E/Y+QZn/3LiDWMPi63EP8MHsXZ4tiIb2w=.ed25519" +``` + You can now login in the web-front-end using these credentials From 4b9fdf2ebac0e8c4930ab84e997dcc18a3e4b1be Mon Sep 17 00:00:00 2001 From: Kyle Maas Date: Fri, 21 Oct 2022 14:02:02 -0400 Subject: [PATCH 3/9] Add instructions for installing Go --- docs/deployment.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/deployment.md b/docs/deployment.md index 7eb638d8..1f03fff0 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -155,6 +155,13 @@ sudo ufw allow 8008/tcp # First Admin user To manage your now working server, you need an initial admin user. For this you can use the "insert-user" utility included with go-ssb-room. + +If you installed the Debian package, you will first need to install Go to build the "insert-user" utility. You can do this via: + +``` +sudo apt-get install golang-go +``` + In a new terminal window navigate to the insert-user utility folder and compile the GO-based utility into an executable your computer can use ``` From 919fd552eb438b1e5f976c2ff910d3d2da77eca0 Mon Sep 17 00:00:00 2001 From: mix irving Date: Mon, 31 Oct 2022 15:34:35 +1300 Subject: [PATCH 4/9] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2d628027..a633afdf 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: CC0-1.0 # Go-SSB Room [![REUSE status](https://api.reuse.software/badge/github.com/ssb-ngi-pointer/go-ssb-room)](https://api.reuse.software/info/github.com/ssb-ngi-pointer/go-ssb-room) -This repository contains code for a [Secure Scuttlebutt](https://ssb.nz) [Room (v1+v2) server](https://github.com/ssb-ngi-pointer/rooms2), written in Go. +This repository implements the [Room (v1+v2) server spec](https://github.com/ssbc/rooms2), in Go. It includes: * secret-handshake+boxstream network transport, sometimes referred to as SHS, using [secretstream](https://github.com/cryptoscope/secretstream) From 158e5129ba611ae677021ceecedd42658253cfc3 Mon Sep 17 00:00:00 2001 From: mix irving Date: Mon, 31 Oct 2022 15:36:09 +1300 Subject: [PATCH 5/9] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a633afdf..2a68319a 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ It includes: * Alias management For a comprehensive introduction to rooms 2.0, 🎥 [watch this video](https://www.youtube.com/watch?v=W5p0y_MWwDE). +For a description of MuxRPC APIs see https://github.com/ssbc/rooms2 ## :rocket: Deployment From ea9b22cfa71e6cda0e3dabe8f904622fcdb23378 Mon Sep 17 00:00:00 2001 From: boreq Date: Mon, 31 Oct 2022 16:42:32 +0100 Subject: [PATCH 6/9] 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. --- muxrpc/handlers/tunnel/server/attendants.go | 2 +- muxrpc/handlers/tunnel/server/members.go | 57 ++++++++++++ muxrpc/handlers/tunnel/server/plugin.go | 3 +- muxrpc/handlers/tunnel/server/state.go | 12 +-- muxrpc/test/go/members_test.go | 96 +++++++++++++++++++++ roomsrv/manifest.go | 1 + 6 files changed, 163 insertions(+), 8 deletions(-) create mode 100644 muxrpc/handlers/tunnel/server/members.go create mode 100644 muxrpc/test/go/members_test.go diff --git a/muxrpc/handlers/tunnel/server/attendants.go b/muxrpc/handlers/tunnel/server/attendants.go index 3a36bbab..cd5026b1 100644 --- a/muxrpc/handlers/tunnel/server/attendants.go +++ b/muxrpc/handlers/tunnel/server/attendants.go @@ -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") } diff --git a/muxrpc/handlers/tunnel/server/members.go b/muxrpc/handlers/tunnel/server/members.go new file mode 100644 index 00000000..307a2854 --- /dev/null +++ b/muxrpc/handlers/tunnel/server/members.go @@ -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() +} diff --git a/muxrpc/handlers/tunnel/server/plugin.go b/muxrpc/handlers/tunnel/server/plugin.go index b8d86b0c..6144b5d3 100644 --- a/muxrpc/handlers/tunnel/server/plugin.go +++ b/muxrpc/handlers/tunnel/server/plugin.go @@ -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, diff --git a/muxrpc/handlers/tunnel/server/state.go b/muxrpc/handlers/tunnel/server/state.go index c2fa60ed..59a01f57 100644 --- a/muxrpc/handlers/tunnel/server/state.go +++ b/muxrpc/handlers/tunnel/server/state.go @@ -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") } diff --git a/muxrpc/test/go/members_test.go b/muxrpc/test/go/members_test.go new file mode 100644 index 00000000..d8bff20b --- /dev/null +++ b/muxrpc/test/go/members_test.go @@ -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, + ) +} diff --git a/roomsrv/manifest.go b/roomsrv/manifest.go index 70530a79..61760c3f 100644 --- a/roomsrv/manifest.go +++ b/roomsrv/manifest.go @@ -45,6 +45,7 @@ const manifest manifestHandler = ` "connect": "duplex", "attendants": "source", + "members": "source", "metadata": "async", "ping": "sync" }, From 72e7d681766be7552912029ac693d2f36d5bba8d Mon Sep 17 00:00:00 2001 From: Filip Borkiewicz Date: Mon, 31 Oct 2022 16:22:03 +0000 Subject: [PATCH 7/9] Update muxrpc/handlers/tunnel/server/members.go Co-authored-by: decentral1se <1991377+decentral1se@users.noreply.github.com> --- muxrpc/handlers/tunnel/server/members.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/muxrpc/handlers/tunnel/server/members.go b/muxrpc/handlers/tunnel/server/members.go index 307a2854..e32a3782 100644 --- a/muxrpc/handlers/tunnel/server/members.go +++ b/muxrpc/handlers/tunnel/server/members.go @@ -32,7 +32,7 @@ func (h *Handler) members(ctx context.Context, req *muxrpc.Request, snk *muxrpc. 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) + return fmt.Errorf("external users are not allowed to list members: %w", err) } } From 0fa646ccdf174ce85cef377c7c8ce2775bec5b2a Mon Sep 17 00:00:00 2001 From: boreq Date: Mon, 31 Oct 2022 18:32:43 +0100 Subject: [PATCH 8/9] Test privacy modes --- muxrpc/test/go/members_test.go | 220 ++++++++++++++++++++++++--------- 1 file changed, 162 insertions(+), 58 deletions(-) diff --git a/muxrpc/test/go/members_test.go b/muxrpc/test/go/members_test.go index d8bff20b..85384399 100644 --- a/muxrpc/test/go/members_test.go +++ b/muxrpc/test/go/members_test.go @@ -21,76 +21,180 @@ import ( // 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), + testCases := []struct { + Name string + PrivacyMode roomdb.PrivacyMode + ExternalCanConnect bool + ExternalCanList bool + }{ + { + Name: "open", + PrivacyMode: roomdb.ModeOpen, + ExternalCanConnect: true, + ExternalCanList: true, + }, + { + Name: "community", + PrivacyMode: roomdb.ModeCommunity, + ExternalCanConnect: true, + ExternalCanList: false, + }, + { + Name: "restricted", + PrivacyMode: roomdb.ModeRestricted, + ExternalCanConnect: false, + ExternalCanList: false, + }, } - session := makeNamedTestBot(t, "srv", ctx, netOpts) - - aliceKey, err := keys.NewKeyPair(nil) - r.NoError(err) + for i := range testCases { + testCase := testCases[i] - bobKey, err := keys.NewKeyPair(nil) - r.NoError(err) + t.Run(testCase.Name, func(t *testing.T) { + t.Parallel() + r := require.New(t) - bobSession := makeNamedTestBot(t, "bob", ctx, append(netOpts, - roomsrv.WithKeyPair(bobKey), - )) + testInit(t) - _, err = session.srv.Members.Add(ctx, aliceKey.Feed, roomdb.RoleMember) - r.NoError(err) + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) - _, err = session.srv.Members.Add(ctx, bobKey.Feed, roomdb.RoleMember) - r.NoError(err) + appKey := make([]byte, 32) + rand.Read(appKey) - // 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) + netOpts := []roomsrv.Option{ + roomsrv.WithAppKey(appKey), + roomsrv.WithContext(ctx), + } - err = bobSession.srv.Network.Connect(ctx, session.srv.Network.GetListenAddr()) - r.NoError(err, "connect A to the Server") + session := makeNamedTestBot(t, "srv", ctx, netOpts) - t.Log("letting handshaking settle..") - time.Sleep(1 * time.Second) + aliceKey, err := keys.NewKeyPair(nil) + r.NoError(err) - clientForServer, ok := bobSession.srv.Network.GetEndpointFor(session.srv.Whoami()) - r.True(ok) + bobKey, err := keys.NewKeyPair(nil) + r.NoError(err) - src, err := clientForServer.Source(ctx, muxrpc.TypeString, muxrpc.Method{"room", "members"}) - r.NoError(err) + _, err = session.srv.Members.Add(ctx, aliceKey.Feed, roomdb.RoleMember) + r.NoError(err) - var responses []server.Member - for src.Next(ctx) { - bytes, err := src.Bytes() - r.NoError(err) + _, err = session.srv.Members.Add(ctx, bobKey.Feed, roomdb.RoleMember) + r.NoError(err) - var members []server.Member - err = json.Unmarshal(bytes, &members) - r.NoError(err) - responses = append(responses, members...) + err = session.srv.Config.SetPrivacyMode(ctx, testCase.PrivacyMode) + r.NoError(err) + + t.Run("member", func(t *testing.T) { + t.Parallel() + r := require.New(t) + + bobSession := makeNamedTestBot(t, "bob", ctx, append(netOpts, + roomsrv.WithKeyPair(bobKey), + )) + + // 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 bob 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.NoError(src.Err()) + r.Equal( + []server.Member{ + { + ID: aliceKey.Feed, + }, + { + ID: bobKey.Feed, + }, + }, + responses, + ) + }) + + t.Run("external", func(t *testing.T) { + t.Parallel() + r := require.New(t) + + carolKey, err := keys.NewKeyPair(nil) + r.NoError(err) + + carolSession := makeNamedTestBot(t, "carol", ctx, append(netOpts, + roomsrv.WithKeyPair(carolKey), + )) + + // allow bots to dial the remote + // side-effect of re-using a room-server as the client + _, err = carolSession.srv.Members.Add(ctx, session.srv.Whoami(), roomdb.RoleMember) + r.NoError(err) + + err = carolSession.srv.Network.Connect(ctx, session.srv.Network.GetListenAddr()) + r.NoError(err, "connect carol to the Server") + + t.Log("letting handshaking settle..") + time.Sleep(1 * time.Second) + + clientForServer, ok := carolSession.srv.Network.GetEndpointFor(session.srv.Whoami()) + if testCase.ExternalCanConnect { + r.True(ok) + } else { + r.False(ok) + return + } + + 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...) + } + + if testCase.ExternalCanList { + r.NoError(src.Err()) + r.Equal( + []server.Member{ + { + ID: aliceKey.Feed, + }, + { + ID: bobKey.Feed, + }, + }, + responses, + ) + } else { + r.EqualError(src.Err(), "muxrpc CallError: Error - external user are not allowed to list members: roomdb: object not found") + } + }) + }) } - - r.Equal( - []server.Member{ - { - ID: aliceKey.Feed, - }, - { - ID: bobKey.Feed, - }, - }, - responses, - ) } From cecadc4402e9e30c57dd4dcbb71368c23bbae85a Mon Sep 17 00:00:00 2001 From: boreq Date: Mon, 31 Oct 2022 18:36:35 +0100 Subject: [PATCH 9/9] Fix --- muxrpc/test/go/members_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/muxrpc/test/go/members_test.go b/muxrpc/test/go/members_test.go index 85384399..5b79abda 100644 --- a/muxrpc/test/go/members_test.go +++ b/muxrpc/test/go/members_test.go @@ -192,7 +192,7 @@ func TestRoomMembers(t *testing.T) { responses, ) } else { - r.EqualError(src.Err(), "muxrpc CallError: Error - external user are not allowed to list members: roomdb: object not found") + r.EqualError(src.Err(), "muxrpc CallError: Error - external users are not allowed to list members: roomdb: object not found") } }) })