Merge pull request #78 from ssb-ngi-pointer/aliases
Aliases: database and web/muxrpc handlers
This commit is contained in:
commit
206a776917
|
@ -0,0 +1,56 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package aliases implements the validation and signing features of https://ssb-ngi-pointer.github.io/rooms2/#alias
|
||||
package aliases
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
)
|
||||
|
||||
// Registration ties an alias to the ID of the user and the RoomID it should be registered on
|
||||
type Registration struct {
|
||||
Alias string
|
||||
UserID refs.FeedRef
|
||||
RoomID refs.FeedRef
|
||||
}
|
||||
|
||||
// Sign takes the public key (belonging to UserID) and returns the signed confirmation
|
||||
func (r Registration) Sign(privKey ed25519.PrivateKey) Confirmation {
|
||||
var conf Confirmation
|
||||
conf.Registration = r
|
||||
msg := r.createRegistrationMessage()
|
||||
conf.Signature = ed25519.Sign(privKey, msg)
|
||||
return conf
|
||||
}
|
||||
|
||||
// createRegistrationMessage returns the string of bytes that should be signed
|
||||
func (r Registration) createRegistrationMessage() []byte {
|
||||
var message bytes.Buffer
|
||||
message.WriteString("=room-alias-registration:")
|
||||
message.WriteString(r.RoomID.Ref())
|
||||
message.WriteString(":")
|
||||
message.WriteString(r.UserID.Ref())
|
||||
message.WriteString(":")
|
||||
message.WriteString(r.Alias)
|
||||
return message.Bytes()
|
||||
}
|
||||
|
||||
// Confirmation combines a registration with the corresponding signature
|
||||
type Confirmation struct {
|
||||
Registration
|
||||
|
||||
Signature []byte
|
||||
}
|
||||
|
||||
// Verify checks that the confirmation is for the expected room and from the expected feed
|
||||
func (c Confirmation) Verify() bool {
|
||||
// re-construct the registration
|
||||
message := c.createRegistrationMessage()
|
||||
|
||||
// check the signature matches
|
||||
return ed25519.Verify(c.UserID.PubKey(), message, c.Signature)
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package aliases
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/maybemod/keys"
|
||||
"github.com/stretchr/testify/require"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
)
|
||||
|
||||
func TestConfirmation(t *testing.T) {
|
||||
r := require.New(t)
|
||||
|
||||
// this is our room, it's not a valid feed but thats fine for this test
|
||||
roomID := refs.FeedRef{
|
||||
ID: bytes.Repeat([]byte("test"), 8),
|
||||
Algo: "test",
|
||||
}
|
||||
|
||||
// to make the test deterministic, decided by fair dice roll.
|
||||
seed := bytes.Repeat([]byte("yeah"), 8)
|
||||
// our user, who will sign the registration
|
||||
userKeyPair, err := keys.NewKeyPair(bytes.NewReader(seed))
|
||||
r.NoError(err)
|
||||
|
||||
// create and fill out the registration for an alias (in this case the name of the test)
|
||||
var valid Registration
|
||||
valid.RoomID = roomID
|
||||
valid.UserID = userKeyPair.Feed
|
||||
valid.Alias = t.Name()
|
||||
|
||||
// internal function to create the registration string
|
||||
msg := valid.createRegistrationMessage()
|
||||
want := "=room-alias-registration:@dGVzdHRlc3R0ZXN0dGVzdHRlc3R0ZXN0dGVzdHRlc3Q=.test:@Rt2aJrtOqWXhBZ5/vlfzeWQ9Bj/z6iT8CMhlr2WWlG4=.ed25519:TestConfirmation"
|
||||
r.Equal(want, string(msg))
|
||||
|
||||
// create the signed confirmation
|
||||
confirmation := valid.Sign(userKeyPair.Pair.Secret)
|
||||
|
||||
yes := confirmation.Verify()
|
||||
r.True(yes, "should be valid for this room and feed")
|
||||
|
||||
// make up another id for the invalid test(s)
|
||||
otherID := refs.FeedRef{
|
||||
ID: bytes.Repeat([]byte("nope"), 8),
|
||||
Algo: "test",
|
||||
}
|
||||
|
||||
confirmation.RoomID = otherID
|
||||
yes = confirmation.Verify()
|
||||
r.False(yes, "should not be valid for another room")
|
||||
|
||||
confirmation.RoomID = roomID // restore
|
||||
confirmation.UserID = otherID
|
||||
yes = confirmation.Verify()
|
||||
r.False(yes, "should not be valid for this room but another feed")
|
||||
|
||||
// puncture the signature to emulate an invalid one
|
||||
confirmation.Signature[0] = confirmation.Signature[0] ^ 1
|
||||
|
||||
yes = confirmation.Verify()
|
||||
r.False(yes, "should not be valid anymore")
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package aliases
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// IsValid decides whether an alias is okay for use or not.
|
||||
// The room spec defines it as _labels valid under RFC 1035_ ( https://ssb-ngi-pointer.github.io/rooms2/#alias-string )
|
||||
// but that can be mostly any string since DNS is a 8bit binary protocol,
|
||||
// as long as it's shorter then 63 charachters.
|
||||
//
|
||||
// Right now it's pretty basic set of characters (a-z, A-Z, 0-9).
|
||||
// In theory we could be more liberal but there is a bunch of stuff to figure out,
|
||||
// like homograph attacks (https://en.wikipedia.org/wiki/IDN_homograph_attack),
|
||||
// if we would decide to allow full utf8 unicode.
|
||||
func IsValid(alias string) bool {
|
||||
if len(alias) > 63 {
|
||||
return false
|
||||
}
|
||||
|
||||
var valid = true
|
||||
for _, char := range alias {
|
||||
if char >= '0' && char <= '9' { // is an ASCII number
|
||||
continue
|
||||
}
|
||||
|
||||
if char >= 'a' && char <= 'z' { // is an ASCII char between a and z
|
||||
continue
|
||||
}
|
||||
|
||||
if char >= 'A' && char <= 'Z' { // is an ASCII upper-case char between a and z
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Println("found", char)
|
||||
valid = false
|
||||
break
|
||||
}
|
||||
return valid
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package aliases
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsValid(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
|
||||
cases := []struct {
|
||||
alias string
|
||||
valid bool
|
||||
}{
|
||||
{"basic", true},
|
||||
{"no spaces", false},
|
||||
{"no.dots", false},
|
||||
{"#*!(! nope", false},
|
||||
|
||||
// too long
|
||||
{"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", false},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
yes := IsValid(tc.alias)
|
||||
a.Equal(tc.valid, yes, "wrong for %d: %s", i, tc.alias)
|
||||
}
|
||||
}
|
|
@ -29,8 +29,8 @@ import (
|
|||
"github.com/unrolled/secure"
|
||||
"go.cryptoscope.co/muxrpc/v2/debug"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb/sqlite"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb/sqlite"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomsrv"
|
||||
mksrv "github.com/ssb-ngi-pointer/go-ssb-room/roomsrv"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/handlers"
|
||||
|
@ -201,7 +201,10 @@ func runroomsrv() error {
|
|||
}
|
||||
|
||||
// create the shs+muxrpc server
|
||||
roomsrv, err := mksrv.New(db.AllowList, opts...)
|
||||
roomsrv, err := mksrv.New(
|
||||
db.AllowList,
|
||||
db.Aliases,
|
||||
opts...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to instantiate ssb server: %w", err)
|
||||
}
|
||||
|
@ -238,10 +241,11 @@ func runroomsrv() error {
|
|||
Domain: httpsDomain,
|
||||
PortHTTPS: uint(portHTTP),
|
||||
PortMUXRPC: uint(portMUXRPC),
|
||||
PubKey: roomsrv.Whoami().PubKey(),
|
||||
RoomID: roomsrv.Whoami(),
|
||||
},
|
||||
roomsrv.StateManager,
|
||||
handlers.Databases{
|
||||
Aliases: db.Aliases,
|
||||
AuthWithSSB: db.AuthWithSSB,
|
||||
AuthFallback: db.AuthFallback,
|
||||
AllowList: db.AllowList,
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package alias implements the muxrpc handlers for alias needs.
|
||||
package alias
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
"go.cryptoscope.co/muxrpc/v2"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/aliases"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/network"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
)
|
||||
|
||||
// Handler implements the muxrpc methods for alias registration and recvocation
|
||||
type Handler struct {
|
||||
logger kitlog.Logger
|
||||
self refs.FeedRef
|
||||
|
||||
db roomdb.AliasService
|
||||
}
|
||||
|
||||
// New returns a fresh alias muxrpc handler
|
||||
func New(log kitlog.Logger, self refs.FeedRef, aliasDB roomdb.AliasService) Handler {
|
||||
var h Handler
|
||||
h.self = self
|
||||
h.logger = log
|
||||
h.db = aliasDB
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
const sigSuffix = ".sig.ed25519"
|
||||
|
||||
// Register is an async muxrpc method handler for registering aliases.
|
||||
// It receives two string arguments over muxrpc (alias and signature),
|
||||
// checks the signature confirmation is correct (for this room and signed by the key of theconnection)
|
||||
// If it is valid, it registers the alias on the roomdb and returns true. If not it returns an error.
|
||||
func (h Handler) Register(ctx context.Context, req *muxrpc.Request) (interface{}, error) {
|
||||
var args []string
|
||||
|
||||
err := json.Unmarshal(req.RawArgs, &args)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("registerAlias: bad request: %w", err)
|
||||
}
|
||||
|
||||
if n := len(args); n != 2 {
|
||||
return nil, fmt.Errorf("registerAlias: expected two arguments got %d", n)
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(args[1], sigSuffix) {
|
||||
return nil, fmt.Errorf("registerAlias: signature does not have the expected suffix")
|
||||
}
|
||||
|
||||
// remove the suffix of the base64 string
|
||||
sig := strings.TrimSuffix(args[1], sigSuffix)
|
||||
|
||||
var confirmation aliases.Confirmation
|
||||
confirmation.RoomID = h.self
|
||||
confirmation.Alias = args[0]
|
||||
confirmation.Signature, err = base64.StdEncoding.DecodeString(sig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("registerAlias: bad signature encoding: %w", err)
|
||||
}
|
||||
|
||||
// check alias is valid
|
||||
if !aliases.IsValid(confirmation.Alias) {
|
||||
return nil, fmt.Errorf("registerAlias: invalid alias")
|
||||
}
|
||||
|
||||
// get the user from the muxrpc connection
|
||||
userID, err := network.GetFeedRefFromAddr(req.RemoteAddr())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
confirmation.UserID = *userID
|
||||
|
||||
// check the signature
|
||||
if !confirmation.Verify() {
|
||||
return nil, fmt.Errorf("registerAlias: invalid signature")
|
||||
}
|
||||
|
||||
err = h.db.Register(ctx, confirmation.Alias, confirmation.UserID, confirmation.Signature)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("registerAlias: could not register alias: %w", err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Revoke checks that the alias is from that user before revoking the alias from the database.
|
||||
func (h Handler) Revoke(ctx context.Context, req *muxrpc.Request) (interface{}, error) {
|
||||
var args []string
|
||||
|
||||
err := json.Unmarshal(req.RawArgs, &args)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("registerAlias: bad request: %w", err)
|
||||
}
|
||||
|
||||
if n := len(args); n != 1 {
|
||||
return nil, fmt.Errorf("registerAlias: expected two arguments got %d", n)
|
||||
}
|
||||
|
||||
// get the user from the muxrpc connection
|
||||
userID, err := network.GetFeedRefFromAddr(req.RemoteAddr())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
alias, err := h.db.Resolve(ctx, args[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !alias.Feed.Equal(userID) {
|
||||
return nil, fmt.Errorf("revokeAlias: not your alias (moderators need to use the web dashboard of the room")
|
||||
}
|
||||
|
||||
err = h.db.Revoke(ctx, alias.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
|
@ -23,9 +23,8 @@ type connectWithOriginArg struct {
|
|||
Origin refs.FeedRef `json:"origin"` // this should be clear from the shs session already
|
||||
}
|
||||
|
||||
func (h *handler) connect(ctx context.Context, req *muxrpc.Request, peerSrc *muxrpc.ByteSource, peerSnk *muxrpc.ByteSink) error {
|
||||
func (h *Handler) connect(ctx context.Context, req *muxrpc.Request, peerSrc *muxrpc.ByteSource, peerSnk *muxrpc.ByteSink) error {
|
||||
// unpack arguments
|
||||
|
||||
var args []connectArg
|
||||
err := json.Unmarshal(req.RawArgs, &args)
|
||||
if err != nil {
|
||||
|
|
|
@ -3,31 +3,14 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
"go.cryptoscope.co/muxrpc/v2"
|
||||
"go.cryptoscope.co/muxrpc/v2/typemux"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/maybemuxrpc"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomstate"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
)
|
||||
|
||||
const name = "tunnel"
|
||||
|
||||
var method muxrpc.Method = muxrpc.Method{name}
|
||||
|
||||
type plugin struct {
|
||||
h muxrpc.Handler
|
||||
log kitlog.Logger
|
||||
}
|
||||
|
||||
func (plugin) Name() string { return name }
|
||||
func (plugin) Method() muxrpc.Method { return method }
|
||||
func (p plugin) Handler() muxrpc.Handler { return p.h }
|
||||
func (plugin) Authorize(net.Conn) bool { return true }
|
||||
|
||||
/* manifest:
|
||||
{
|
||||
"announce": "sync",
|
||||
|
@ -39,25 +22,23 @@ func (plugin) Authorize(net.Conn) bool { return true }
|
|||
}
|
||||
*/
|
||||
|
||||
func New(log kitlog.Logger, self refs.FeedRef, m *roomstate.Manager) maybemuxrpc.Plugin {
|
||||
mux := typemux.New(log)
|
||||
|
||||
var h = new(handler)
|
||||
func New(log kitlog.Logger, self refs.FeedRef, m *roomstate.Manager) *Handler {
|
||||
var h = new(Handler)
|
||||
h.self = self
|
||||
h.logger = log
|
||||
h.state = m
|
||||
|
||||
mux.RegisterAsync(append(method, "isRoom"), typemux.AsyncFunc(h.isRoom))
|
||||
mux.RegisterAsync(append(method, "ping"), typemux.AsyncFunc(h.ping))
|
||||
|
||||
mux.RegisterAsync(append(method, "announce"), typemux.AsyncFunc(h.announce))
|
||||
mux.RegisterAsync(append(method, "leave"), typemux.AsyncFunc(h.leave))
|
||||
|
||||
mux.RegisterSource(append(method, "endpoints"), typemux.SourceFunc(h.endpoints))
|
||||
|
||||
mux.RegisterDuplex(append(method, "connect"), typemux.DuplexFunc(h.connect))
|
||||
|
||||
return plugin{
|
||||
h: &mux,
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *Handler) Register(mux typemux.HandlerMux, namespace muxrpc.Method) {
|
||||
mux.RegisterAsync(append(namespace, "isRoom"), typemux.AsyncFunc(h.isRoom))
|
||||
mux.RegisterAsync(append(namespace, "ping"), typemux.AsyncFunc(h.ping))
|
||||
|
||||
mux.RegisterAsync(append(namespace, "announce"), typemux.AsyncFunc(h.announce))
|
||||
mux.RegisterAsync(append(namespace, "leave"), typemux.AsyncFunc(h.leave))
|
||||
|
||||
mux.RegisterSource(append(namespace, "endpoints"), typemux.SourceFunc(h.endpoints))
|
||||
|
||||
mux.RegisterDuplex(append(namespace, "connect"), typemux.DuplexFunc(h.connect))
|
||||
}
|
||||
|
|
|
@ -16,25 +16,25 @@ import (
|
|||
"go.cryptoscope.co/muxrpc/v2"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
type Handler struct {
|
||||
logger kitlog.Logger
|
||||
self refs.FeedRef
|
||||
|
||||
state *roomstate.Manager
|
||||
}
|
||||
|
||||
func (h *handler) isRoom(context.Context, *muxrpc.Request) (interface{}, error) {
|
||||
func (h *Handler) isRoom(context.Context, *muxrpc.Request) (interface{}, error) {
|
||||
level.Debug(h.logger).Log("called", "isRoom")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (h *handler) ping(context.Context, *muxrpc.Request) (interface{}, error) {
|
||||
func (h *Handler) ping(context.Context, *muxrpc.Request) (interface{}, error) {
|
||||
now := time.Now().UnixNano() / 1000
|
||||
level.Debug(h.logger).Log("called", "ping")
|
||||
return now, nil
|
||||
}
|
||||
|
||||
func (h *handler) announce(_ context.Context, req *muxrpc.Request) (interface{}, error) {
|
||||
func (h *Handler) announce(_ context.Context, req *muxrpc.Request) (interface{}, error) {
|
||||
level.Debug(h.logger).Log("called", "announce")
|
||||
ref, err := network.GetFeedRefFromAddr(req.RemoteAddr())
|
||||
if err != nil {
|
||||
|
@ -46,7 +46,7 @@ func (h *handler) announce(_ context.Context, req *muxrpc.Request) (interface{},
|
|||
return false, nil
|
||||
}
|
||||
|
||||
func (h *handler) leave(_ context.Context, req *muxrpc.Request) (interface{}, error) {
|
||||
func (h *Handler) leave(_ context.Context, req *muxrpc.Request) (interface{}, error) {
|
||||
ref, err := network.GetFeedRefFromAddr(req.RemoteAddr())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -57,7 +57,7 @@ func (h *handler) leave(_ context.Context, req *muxrpc.Request) (interface{}, er
|
|||
return false, nil
|
||||
}
|
||||
|
||||
func (h *handler) endpoints(_ context.Context, req *muxrpc.Request, snk *muxrpc.ByteSink) error {
|
||||
func (h *Handler) endpoints(_ context.Context, req *muxrpc.Request, snk *muxrpc.ByteSink) error {
|
||||
level.Debug(h.logger).Log("called", "endpoints")
|
||||
|
||||
toPeer := newForwarder(snk)
|
||||
|
|
|
@ -4,15 +4,14 @@ package whoami
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"go.cryptoscope.co/muxrpc/v2/typemux"
|
||||
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
"github.com/go-kit/kit/log/level"
|
||||
"go.cryptoscope.co/muxrpc/v2"
|
||||
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/maybemuxrpc"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -25,64 +24,18 @@ func checkAndLog(log kitlog.Logger, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
func New(log kitlog.Logger, id refs.FeedRef) maybemuxrpc.Plugin {
|
||||
return plugin{handler{
|
||||
log: log,
|
||||
id: id,
|
||||
}}
|
||||
func New(id refs.FeedRef) typemux.AsyncHandler {
|
||||
return handler{id: id}
|
||||
}
|
||||
|
||||
type plugin struct {
|
||||
h handler
|
||||
}
|
||||
|
||||
func (plugin) Name() string { return "whoami" }
|
||||
|
||||
func (plugin) Method() muxrpc.Method { return method }
|
||||
|
||||
func (wami plugin) Handler() muxrpc.Handler { return wami.h }
|
||||
|
||||
func (plugin) Authorize(net.Conn) bool { return true }
|
||||
|
||||
type handler struct {
|
||||
log kitlog.Logger
|
||||
id refs.FeedRef
|
||||
id refs.FeedRef
|
||||
}
|
||||
|
||||
func (handler) Handled(m muxrpc.Method) bool { return m.String() == "whoami" }
|
||||
|
||||
func (handler) HandleConnect(ctx context.Context, edp muxrpc.Endpoint) {}
|
||||
|
||||
func (h handler) HandleCall(ctx context.Context, req *muxrpc.Request) {
|
||||
// TODO: push manifest check into muxrpc
|
||||
if req.Type == "" {
|
||||
req.Type = "async"
|
||||
}
|
||||
if req.Method.String() != "whoami" {
|
||||
req.CloseWithError(fmt.Errorf("wrong method"))
|
||||
return
|
||||
}
|
||||
func (h handler) HandleAsync(ctx context.Context, req *muxrpc.Request) (interface{}, error) {
|
||||
type ret struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
err := req.Return(ctx, ret{h.id.Ref()})
|
||||
checkAndLog(h.log, err)
|
||||
}
|
||||
|
||||
type endpoint struct {
|
||||
edp muxrpc.Endpoint
|
||||
}
|
||||
|
||||
func (edp endpoint) WhoAmI(ctx context.Context) (refs.FeedRef, error) {
|
||||
var resp struct {
|
||||
ID refs.FeedRef `json:"id"`
|
||||
}
|
||||
|
||||
err := edp.edp.Async(ctx, &resp, muxrpc.TypeJSON, method)
|
||||
if err != nil {
|
||||
return refs.FeedRef{}, fmt.Errorf("error making async call: %w", err)
|
||||
}
|
||||
|
||||
return resp.ID, nil
|
||||
return ret{h.id.Ref()}, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package go_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.cryptoscope.co/muxrpc/v2"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/aliases"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/maybemod/keys"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomsrv"
|
||||
)
|
||||
|
||||
// technically we are usign two servers here
|
||||
// but we just treat one of them as a muxrpc client
|
||||
func TestAliasRegister(t *testing.T) {
|
||||
testInit(t)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
botgroup, ctx := errgroup.WithContext(ctx)
|
||||
bs := newBotServer(ctx, mainLog)
|
||||
|
||||
r := require.New(t)
|
||||
a := assert.New(t)
|
||||
|
||||
// make a random test key
|
||||
appKey := make([]byte, 32)
|
||||
rand.Read(appKey)
|
||||
|
||||
netOpts := []roomsrv.Option{
|
||||
roomsrv.WithAppKey(appKey),
|
||||
roomsrv.WithContext(ctx),
|
||||
}
|
||||
|
||||
theBots := []*roomsrv.Server{}
|
||||
|
||||
serv := makeNamedTestBot(t, "srv", netOpts)
|
||||
botgroup.Go(bs.Serve(serv))
|
||||
theBots = append(theBots, serv)
|
||||
|
||||
// we need bobs key to create the signature
|
||||
bobsKey, err := keys.NewKeyPair(nil)
|
||||
r.NoError(err)
|
||||
|
||||
bob := makeNamedTestBot(t, "bob", append(netOpts,
|
||||
roomsrv.WithKeyPair(bobsKey),
|
||||
))
|
||||
botgroup.Go(bs.Serve(bob))
|
||||
theBots = append(theBots, bob)
|
||||
|
||||
t.Cleanup(func() {
|
||||
for _, bot := range theBots {
|
||||
bot.Shutdown()
|
||||
r.NoError(bot.Close())
|
||||
}
|
||||
r.NoError(botgroup.Wait())
|
||||
})
|
||||
|
||||
// adds
|
||||
serv.Allow(bob.Whoami(), true)
|
||||
// serv.Allow(botB.Whoami(), true)
|
||||
|
||||
// allow bots to dial the remote
|
||||
bob.Allow(serv.Whoami(), true)
|
||||
// botB.Allow(serv.Whoami(), true)
|
||||
|
||||
// should work (we allowed A)
|
||||
err = bob.Network.Connect(ctx, serv.Network.GetListenAddr())
|
||||
r.NoError(err, "connect A to the Server")
|
||||
|
||||
t.Log("letting handshaking settle..")
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
clientForServer, ok := bob.Network.GetEndpointFor(serv.Whoami())
|
||||
r.True(ok)
|
||||
|
||||
t.Log("got endpoint")
|
||||
|
||||
var testReg aliases.Registration
|
||||
testReg.Alias = "bob"
|
||||
testReg.RoomID = serv.Whoami()
|
||||
testReg.UserID = bob.Whoami()
|
||||
|
||||
confirmation := testReg.Sign(bobsKey.Pair.Secret)
|
||||
t.Logf("signature created: %x...", confirmation.Signature[:16])
|
||||
|
||||
// encode the signature as base64
|
||||
sig := base64.StdEncoding.EncodeToString(confirmation.Signature) + ".sig.ed25519"
|
||||
|
||||
var worked bool
|
||||
err = clientForServer.Async(ctx, &worked, muxrpc.TypeJSON, muxrpc.Method{"room", "registerAlias"}, "bob", sig)
|
||||
r.NoError(err)
|
||||
a.True(worked)
|
||||
|
||||
// server should have the alias now
|
||||
alias, err := serv.Aliases.Resolve(ctx, "bob")
|
||||
r.NoError(err)
|
||||
|
||||
a.Equal(confirmation.Alias, alias.Name)
|
||||
a.Equal(confirmation.Signature, alias.Signature)
|
||||
a.True(confirmation.UserID.Equal(&bobsKey.Feed))
|
||||
|
||||
t.Log("alias stored")
|
||||
|
||||
cancel()
|
||||
}
|
|
@ -18,10 +18,10 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
"go.cryptoscope.co/muxrpc/v2/debug"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb/sqlite"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/maybemod/testutils"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/network"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb/sqlite"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomsrv"
|
||||
)
|
||||
|
||||
|
@ -82,9 +82,11 @@ func makeNamedTestBot(t testing.TB, name string, opts []roomsrv.Option) *roomsrv
|
|||
db, err := sqlite.Open(repo.New(testPath))
|
||||
r.NoError(err)
|
||||
t.Cleanup(func() {
|
||||
db.Close()
|
||||
if err := db.Close(); err != nil {
|
||||
t.Log("db close failed: ", err)
|
||||
}
|
||||
})
|
||||
theBot, err := roomsrv.New(db.AllowList, botOptions...)
|
||||
theBot, err := roomsrv.New(db.AllowList, db.Aliases, botOptions...)
|
||||
r.NoError(err)
|
||||
return theBot
|
||||
}
|
||||
|
|
|
@ -80,8 +80,9 @@ func TestJSClient(t *testing.T) {
|
|||
ts := newRandomSession(t)
|
||||
// ts := newSession(t, nil)
|
||||
|
||||
var al = &mockdb.FakeAllowListService{}
|
||||
srv := ts.startGoServer(al)
|
||||
var allowDB = &mockdb.FakeAllowListService{}
|
||||
var aliasDB = &mockdb.FakeAliasService{}
|
||||
srv := ts.startGoServer(allowDB, aliasDB)
|
||||
|
||||
alice := ts.startJSClient("alice", "./testscripts/simple_client.js",
|
||||
srv.Network.GetListenAddr(),
|
||||
|
@ -110,7 +111,7 @@ func TestJSClient(t *testing.T) {
|
|||
)
|
||||
|
||||
srv.Allow(bob, true)
|
||||
al.HasFeedReturns(true)
|
||||
allowDB.HasFeedReturns(true)
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
|
@ -137,10 +138,11 @@ func TestJSServer(t *testing.T) {
|
|||
}
|
||||
|
||||
// now connect our go client
|
||||
var al = &mockdb.FakeAllowListService{}
|
||||
client := ts.startGoServer(al)
|
||||
var allowDB = &mockdb.FakeAllowListService{}
|
||||
var aliasDB = &mockdb.FakeAliasService{}
|
||||
client := ts.startGoServer(allowDB, aliasDB)
|
||||
client.Allow(*alice, true)
|
||||
al.HasFeedReturns(true)
|
||||
allowDB.HasFeedReturns(true)
|
||||
|
||||
var roomHandle bytes.Buffer
|
||||
roomHandle.WriteString("tunnel:")
|
||||
|
|
|
@ -86,7 +86,10 @@ func newSession(t *testing.T, appKey []byte) *testSession {
|
|||
return ts
|
||||
}
|
||||
|
||||
func (ts *testSession) startGoServer(al roomdb.AllowListService, opts ...roomsrv.Option) *roomsrv.Server {
|
||||
func (ts *testSession) startGoServer(
|
||||
allowDB roomdb.AllowListService,
|
||||
aliasDB roomdb.AliasService,
|
||||
opts ...roomsrv.Option) *roomsrv.Server {
|
||||
r := require.New(ts.t)
|
||||
|
||||
// prepend defaults
|
||||
|
@ -107,7 +110,7 @@ func (ts *testSession) startGoServer(al roomdb.AllowListService, opts ...roomsrv
|
|||
}),
|
||||
)
|
||||
|
||||
srv, err := roomsrv.New(al, opts...)
|
||||
srv, err := roomsrv.New(allowDB, aliasDB, opts...)
|
||||
r.NoError(err, "failed to init tees a server")
|
||||
ts.t.Logf("go server: %s", srv.Whoami().Ref())
|
||||
ts.t.Cleanup(func() {
|
||||
|
|
|
@ -53,7 +53,22 @@ type AllowListService interface {
|
|||
}
|
||||
|
||||
// AliasService manages alias handle registration and lookup
|
||||
type AliasService interface{}
|
||||
type AliasService interface {
|
||||
// Resolve returns all the relevant information for that alias or an error if it doesnt exist
|
||||
Resolve(context.Context, string) (Alias, error)
|
||||
|
||||
// GetByID returns the alias for that ID or an error
|
||||
GetByID(context.Context, int64) (Alias, error)
|
||||
|
||||
// List returns a list of all registerd aliases
|
||||
List(ctx context.Context) ([]Alias, error)
|
||||
|
||||
// Register receives an alias and signature for it. Validation needs to happen before this.
|
||||
Register(ctx context.Context, alias string, userFeed refs.FeedRef, signature []byte) error
|
||||
|
||||
// Revoke removes an alias from the system
|
||||
Revoke(ctx context.Context, alias string) error
|
||||
}
|
||||
|
||||
// InviteService manages creation and consumption of invite tokens for joining the room.
|
||||
type InviteService interface {
|
||||
|
|
|
@ -2,19 +2,423 @@
|
|||
package mockdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
)
|
||||
|
||||
type FakeAliasService struct {
|
||||
GetByIDStub func(context.Context, int64) (roomdb.Alias, error)
|
||||
getByIDMutex sync.RWMutex
|
||||
getByIDArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 int64
|
||||
}
|
||||
getByIDReturns struct {
|
||||
result1 roomdb.Alias
|
||||
result2 error
|
||||
}
|
||||
getByIDReturnsOnCall map[int]struct {
|
||||
result1 roomdb.Alias
|
||||
result2 error
|
||||
}
|
||||
ListStub func(context.Context) ([]roomdb.Alias, error)
|
||||
listMutex sync.RWMutex
|
||||
listArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
}
|
||||
listReturns struct {
|
||||
result1 []roomdb.Alias
|
||||
result2 error
|
||||
}
|
||||
listReturnsOnCall map[int]struct {
|
||||
result1 []roomdb.Alias
|
||||
result2 error
|
||||
}
|
||||
RegisterStub func(context.Context, string, refs.FeedRef, []byte) error
|
||||
registerMutex sync.RWMutex
|
||||
registerArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 string
|
||||
arg3 refs.FeedRef
|
||||
arg4 []byte
|
||||
}
|
||||
registerReturns struct {
|
||||
result1 error
|
||||
}
|
||||
registerReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
ResolveStub func(context.Context, string) (roomdb.Alias, error)
|
||||
resolveMutex sync.RWMutex
|
||||
resolveArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 string
|
||||
}
|
||||
resolveReturns struct {
|
||||
result1 roomdb.Alias
|
||||
result2 error
|
||||
}
|
||||
resolveReturnsOnCall map[int]struct {
|
||||
result1 roomdb.Alias
|
||||
result2 error
|
||||
}
|
||||
RevokeStub func(context.Context, string) error
|
||||
revokeMutex sync.RWMutex
|
||||
revokeArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 string
|
||||
}
|
||||
revokeReturns struct {
|
||||
result1 error
|
||||
}
|
||||
revokeReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
invocations map[string][][]interface{}
|
||||
invocationsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) GetByID(arg1 context.Context, arg2 int64) (roomdb.Alias, error) {
|
||||
fake.getByIDMutex.Lock()
|
||||
ret, specificReturn := fake.getByIDReturnsOnCall[len(fake.getByIDArgsForCall)]
|
||||
fake.getByIDArgsForCall = append(fake.getByIDArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 int64
|
||||
}{arg1, arg2})
|
||||
stub := fake.GetByIDStub
|
||||
fakeReturns := fake.getByIDReturns
|
||||
fake.recordInvocation("GetByID", []interface{}{arg1, arg2})
|
||||
fake.getByIDMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) GetByIDCallCount() int {
|
||||
fake.getByIDMutex.RLock()
|
||||
defer fake.getByIDMutex.RUnlock()
|
||||
return len(fake.getByIDArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) GetByIDCalls(stub func(context.Context, int64) (roomdb.Alias, error)) {
|
||||
fake.getByIDMutex.Lock()
|
||||
defer fake.getByIDMutex.Unlock()
|
||||
fake.GetByIDStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) GetByIDArgsForCall(i int) (context.Context, int64) {
|
||||
fake.getByIDMutex.RLock()
|
||||
defer fake.getByIDMutex.RUnlock()
|
||||
argsForCall := fake.getByIDArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) GetByIDReturns(result1 roomdb.Alias, result2 error) {
|
||||
fake.getByIDMutex.Lock()
|
||||
defer fake.getByIDMutex.Unlock()
|
||||
fake.GetByIDStub = nil
|
||||
fake.getByIDReturns = struct {
|
||||
result1 roomdb.Alias
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) GetByIDReturnsOnCall(i int, result1 roomdb.Alias, result2 error) {
|
||||
fake.getByIDMutex.Lock()
|
||||
defer fake.getByIDMutex.Unlock()
|
||||
fake.GetByIDStub = nil
|
||||
if fake.getByIDReturnsOnCall == nil {
|
||||
fake.getByIDReturnsOnCall = make(map[int]struct {
|
||||
result1 roomdb.Alias
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.getByIDReturnsOnCall[i] = struct {
|
||||
result1 roomdb.Alias
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) List(arg1 context.Context) ([]roomdb.Alias, error) {
|
||||
fake.listMutex.Lock()
|
||||
ret, specificReturn := fake.listReturnsOnCall[len(fake.listArgsForCall)]
|
||||
fake.listArgsForCall = append(fake.listArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
}{arg1})
|
||||
stub := fake.ListStub
|
||||
fakeReturns := fake.listReturns
|
||||
fake.recordInvocation("List", []interface{}{arg1})
|
||||
fake.listMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) ListCallCount() int {
|
||||
fake.listMutex.RLock()
|
||||
defer fake.listMutex.RUnlock()
|
||||
return len(fake.listArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) ListCalls(stub func(context.Context) ([]roomdb.Alias, error)) {
|
||||
fake.listMutex.Lock()
|
||||
defer fake.listMutex.Unlock()
|
||||
fake.ListStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) ListArgsForCall(i int) context.Context {
|
||||
fake.listMutex.RLock()
|
||||
defer fake.listMutex.RUnlock()
|
||||
argsForCall := fake.listArgsForCall[i]
|
||||
return argsForCall.arg1
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) ListReturns(result1 []roomdb.Alias, result2 error) {
|
||||
fake.listMutex.Lock()
|
||||
defer fake.listMutex.Unlock()
|
||||
fake.ListStub = nil
|
||||
fake.listReturns = struct {
|
||||
result1 []roomdb.Alias
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) ListReturnsOnCall(i int, result1 []roomdb.Alias, result2 error) {
|
||||
fake.listMutex.Lock()
|
||||
defer fake.listMutex.Unlock()
|
||||
fake.ListStub = nil
|
||||
if fake.listReturnsOnCall == nil {
|
||||
fake.listReturnsOnCall = make(map[int]struct {
|
||||
result1 []roomdb.Alias
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.listReturnsOnCall[i] = struct {
|
||||
result1 []roomdb.Alias
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) Register(arg1 context.Context, arg2 string, arg3 refs.FeedRef, arg4 []byte) error {
|
||||
var arg4Copy []byte
|
||||
if arg4 != nil {
|
||||
arg4Copy = make([]byte, len(arg4))
|
||||
copy(arg4Copy, arg4)
|
||||
}
|
||||
fake.registerMutex.Lock()
|
||||
ret, specificReturn := fake.registerReturnsOnCall[len(fake.registerArgsForCall)]
|
||||
fake.registerArgsForCall = append(fake.registerArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 string
|
||||
arg3 refs.FeedRef
|
||||
arg4 []byte
|
||||
}{arg1, arg2, arg3, arg4Copy})
|
||||
stub := fake.RegisterStub
|
||||
fakeReturns := fake.registerReturns
|
||||
fake.recordInvocation("Register", []interface{}{arg1, arg2, arg3, arg4Copy})
|
||||
fake.registerMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2, arg3, arg4)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) RegisterCallCount() int {
|
||||
fake.registerMutex.RLock()
|
||||
defer fake.registerMutex.RUnlock()
|
||||
return len(fake.registerArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) RegisterCalls(stub func(context.Context, string, refs.FeedRef, []byte) error) {
|
||||
fake.registerMutex.Lock()
|
||||
defer fake.registerMutex.Unlock()
|
||||
fake.RegisterStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) RegisterArgsForCall(i int) (context.Context, string, refs.FeedRef, []byte) {
|
||||
fake.registerMutex.RLock()
|
||||
defer fake.registerMutex.RUnlock()
|
||||
argsForCall := fake.registerArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) RegisterReturns(result1 error) {
|
||||
fake.registerMutex.Lock()
|
||||
defer fake.registerMutex.Unlock()
|
||||
fake.RegisterStub = nil
|
||||
fake.registerReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) RegisterReturnsOnCall(i int, result1 error) {
|
||||
fake.registerMutex.Lock()
|
||||
defer fake.registerMutex.Unlock()
|
||||
fake.RegisterStub = nil
|
||||
if fake.registerReturnsOnCall == nil {
|
||||
fake.registerReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.registerReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) Resolve(arg1 context.Context, arg2 string) (roomdb.Alias, error) {
|
||||
fake.resolveMutex.Lock()
|
||||
ret, specificReturn := fake.resolveReturnsOnCall[len(fake.resolveArgsForCall)]
|
||||
fake.resolveArgsForCall = append(fake.resolveArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 string
|
||||
}{arg1, arg2})
|
||||
stub := fake.ResolveStub
|
||||
fakeReturns := fake.resolveReturns
|
||||
fake.recordInvocation("Resolve", []interface{}{arg1, arg2})
|
||||
fake.resolveMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) ResolveCallCount() int {
|
||||
fake.resolveMutex.RLock()
|
||||
defer fake.resolveMutex.RUnlock()
|
||||
return len(fake.resolveArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) ResolveCalls(stub func(context.Context, string) (roomdb.Alias, error)) {
|
||||
fake.resolveMutex.Lock()
|
||||
defer fake.resolveMutex.Unlock()
|
||||
fake.ResolveStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) ResolveArgsForCall(i int) (context.Context, string) {
|
||||
fake.resolveMutex.RLock()
|
||||
defer fake.resolveMutex.RUnlock()
|
||||
argsForCall := fake.resolveArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) ResolveReturns(result1 roomdb.Alias, result2 error) {
|
||||
fake.resolveMutex.Lock()
|
||||
defer fake.resolveMutex.Unlock()
|
||||
fake.ResolveStub = nil
|
||||
fake.resolveReturns = struct {
|
||||
result1 roomdb.Alias
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) ResolveReturnsOnCall(i int, result1 roomdb.Alias, result2 error) {
|
||||
fake.resolveMutex.Lock()
|
||||
defer fake.resolveMutex.Unlock()
|
||||
fake.ResolveStub = nil
|
||||
if fake.resolveReturnsOnCall == nil {
|
||||
fake.resolveReturnsOnCall = make(map[int]struct {
|
||||
result1 roomdb.Alias
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.resolveReturnsOnCall[i] = struct {
|
||||
result1 roomdb.Alias
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) Revoke(arg1 context.Context, arg2 string) error {
|
||||
fake.revokeMutex.Lock()
|
||||
ret, specificReturn := fake.revokeReturnsOnCall[len(fake.revokeArgsForCall)]
|
||||
fake.revokeArgsForCall = append(fake.revokeArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 string
|
||||
}{arg1, arg2})
|
||||
stub := fake.RevokeStub
|
||||
fakeReturns := fake.revokeReturns
|
||||
fake.recordInvocation("Revoke", []interface{}{arg1, arg2})
|
||||
fake.revokeMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) RevokeCallCount() int {
|
||||
fake.revokeMutex.RLock()
|
||||
defer fake.revokeMutex.RUnlock()
|
||||
return len(fake.revokeArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) RevokeCalls(stub func(context.Context, string) error) {
|
||||
fake.revokeMutex.Lock()
|
||||
defer fake.revokeMutex.Unlock()
|
||||
fake.RevokeStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) RevokeArgsForCall(i int) (context.Context, string) {
|
||||
fake.revokeMutex.RLock()
|
||||
defer fake.revokeMutex.RUnlock()
|
||||
argsForCall := fake.revokeArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) RevokeReturns(result1 error) {
|
||||
fake.revokeMutex.Lock()
|
||||
defer fake.revokeMutex.Unlock()
|
||||
fake.RevokeStub = nil
|
||||
fake.revokeReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) RevokeReturnsOnCall(i int, result1 error) {
|
||||
fake.revokeMutex.Lock()
|
||||
defer fake.revokeMutex.Unlock()
|
||||
fake.RevokeStub = nil
|
||||
if fake.revokeReturnsOnCall == nil {
|
||||
fake.revokeReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.revokeReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeAliasService) Invocations() map[string][][]interface{} {
|
||||
fake.invocationsMutex.RLock()
|
||||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.getByIDMutex.RLock()
|
||||
defer fake.getByIDMutex.RUnlock()
|
||||
fake.listMutex.RLock()
|
||||
defer fake.listMutex.RUnlock()
|
||||
fake.registerMutex.RLock()
|
||||
defer fake.registerMutex.RUnlock()
|
||||
fake.resolveMutex.RLock()
|
||||
defer fake.resolveMutex.RUnlock()
|
||||
fake.revokeMutex.RLock()
|
||||
defer fake.revokeMutex.RUnlock()
|
||||
copiedInvocations := map[string][][]interface{}{}
|
||||
for key, value := range fake.invocations {
|
||||
copiedInvocations[key] = value
|
||||
|
|
|
@ -3,9 +3,16 @@
|
|||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/friendsofgo/errors"
|
||||
"github.com/volatiletech/sqlboiler/v4/boil"
|
||||
"github.com/volatiletech/sqlboiler/v4/queries/qm"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb/sqlite/models"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
)
|
||||
|
||||
// compiler assertion to ensure the struct fullfills the interface
|
||||
|
@ -14,3 +21,98 @@ var _ roomdb.AliasService = (*Aliases)(nil)
|
|||
type Aliases struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
// Resolve returns all the relevant information for that alias or an error if it doesnt exist
|
||||
func (a Aliases) Resolve(ctx context.Context, name string) (roomdb.Alias, error) {
|
||||
return a.findOne(ctx, qm.Where("name = ?", name))
|
||||
}
|
||||
|
||||
// GetByID returns the alias for that ID or an error
|
||||
func (a Aliases) GetByID(ctx context.Context, id int64) (roomdb.Alias, error) {
|
||||
return a.findOne(ctx, qm.Where("id = ?", id))
|
||||
}
|
||||
|
||||
func (a Aliases) findOne(ctx context.Context, by qm.QueryMod) (roomdb.Alias, error) {
|
||||
var found roomdb.Alias
|
||||
|
||||
// construct query which resolves the User relation and by which we shoudl look for it
|
||||
qry := append([]qm.QueryMod{qm.Load("User")}, by)
|
||||
|
||||
entry, err := models.Aliases(qry...).One(ctx, a.db)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return found, roomdb.ErrNotFound
|
||||
}
|
||||
return found, err
|
||||
}
|
||||
|
||||
// unpack models into roomdb type
|
||||
found.ID = entry.ID
|
||||
found.Name = entry.Name
|
||||
found.Signature = entry.Signature
|
||||
found.Feed = entry.R.User.PubKey.FeedRef
|
||||
|
||||
return found, nil
|
||||
}
|
||||
|
||||
// List returns a list of all registerd aliases
|
||||
func (a Aliases) List(ctx context.Context) ([]roomdb.Alias, error) {
|
||||
all, err := models.Aliases(qm.Load("User")).All(ctx, a.db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var aliases = make([]roomdb.Alias, len(all))
|
||||
for i, entry := range all {
|
||||
|
||||
aliases[i] = roomdb.Alias{
|
||||
ID: entry.ID,
|
||||
Name: entry.Name,
|
||||
Feed: entry.R.User.PubKey.FeedRef,
|
||||
Signature: entry.Signature,
|
||||
}
|
||||
}
|
||||
|
||||
return aliases, nil
|
||||
}
|
||||
|
||||
// Register receives an alias and signature for it. Validation needs to happen before this.
|
||||
func (a Aliases) Register(ctx context.Context, alias string, userFeed refs.FeedRef, signature []byte) error {
|
||||
return transact(a.db, func(tx *sql.Tx) error {
|
||||
// check we have a members entry for the feed and load it to get its ID
|
||||
allowListEntry, err := models.AllowLists(qm.Where("pub_key = ?", userFeed.Ref())).One(ctx, tx)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return roomdb.ErrNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var newEntry models.Alias
|
||||
newEntry.Name = alias
|
||||
newEntry.UserID = allowListEntry.ID
|
||||
newEntry.Signature = signature
|
||||
|
||||
err = newEntry.Insert(ctx, tx, boil.Infer())
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// Revoke removes an alias from the system
|
||||
func (a Aliases) Revoke(ctx context.Context, alias string) error {
|
||||
return transact(a.db, func(tx *sql.Tx) error {
|
||||
qry := append([]qm.QueryMod{qm.Load("User")}, qm.Where("name = ?", alias))
|
||||
|
||||
entry, err := models.Aliases(qry...).One(ctx, a.db)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return roomdb.ErrNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = entry.Delete(ctx, tx)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
)
|
||||
|
||||
func TestAliases(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
testRepo := filepath.Join("testrun", t.Name())
|
||||
os.RemoveAll(testRepo)
|
||||
tr := repo.New(testRepo)
|
||||
|
||||
// fake feed for testing, looks ok at least
|
||||
newMember := refs.FeedRef{ID: bytes.Repeat([]byte("acab"), 8), Algo: refs.RefAlgoFeedSSB1}
|
||||
|
||||
// 64 bytes of random for testing (validation is handled by the handlers)
|
||||
testSig := make([]byte, 64)
|
||||
rand.Read(testSig)
|
||||
|
||||
db, err := Open(tr)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
r := require.New(t)
|
||||
|
||||
lst, err := db.Aliases.List(ctx)
|
||||
r.NoError(err)
|
||||
r.Len(lst, 0)
|
||||
|
||||
_, err = db.Aliases.GetByID(ctx, 9999)
|
||||
r.Error(err)
|
||||
r.EqualError(err, roomdb.ErrNotFound.Error())
|
||||
|
||||
_, err = db.Aliases.Resolve(ctx, "unknown")
|
||||
r.Error(err)
|
||||
r.EqualError(err, roomdb.ErrNotFound.Error())
|
||||
|
||||
err = db.Aliases.Revoke(ctx, "unknown")
|
||||
r.Error(err)
|
||||
r.EqualError(errors.Unwrap(err), roomdb.ErrNotFound.Error())
|
||||
})
|
||||
|
||||
t.Run("register and revoke again", func(t *testing.T) {
|
||||
r := require.New(t)
|
||||
|
||||
testName := "flaky"
|
||||
|
||||
// shouldnt work while not a member
|
||||
err = db.Aliases.Register(ctx, testName, newMember, testSig)
|
||||
r.Error(err)
|
||||
|
||||
// allow the member
|
||||
err = db.AllowList.Add(ctx, newMember)
|
||||
r.NoError(err)
|
||||
|
||||
err = db.Aliases.Register(ctx, testName, newMember, testSig)
|
||||
r.NoError(err)
|
||||
|
||||
// should have one member now
|
||||
lst, err := db.Aliases.List(ctx)
|
||||
r.NoError(err)
|
||||
r.Len(lst, 1)
|
||||
|
||||
aliasByID, err := db.Aliases.GetByID(ctx, lst[0].ID)
|
||||
r.NoError(err)
|
||||
r.Equal(testName, aliasByID.Name)
|
||||
r.Equal(testSig, aliasByID.Signature)
|
||||
|
||||
resolvedAlias, err := db.Aliases.Resolve(ctx, testName)
|
||||
r.NoError(err)
|
||||
r.Equal(aliasByID, resolvedAlias)
|
||||
|
||||
err = db.Aliases.Revoke(ctx, testName)
|
||||
r.NoError(err)
|
||||
|
||||
_, err = db.Aliases.GetByID(ctx, lst[0].ID)
|
||||
r.Error(err)
|
||||
r.EqualError(err, roomdb.ErrNotFound.Error())
|
||||
|
||||
_, err = db.Aliases.Resolve(ctx, testName)
|
||||
r.Error(err)
|
||||
r.EqualError(err, roomdb.ErrNotFound.Error())
|
||||
})
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
-- +migrate Up
|
||||
CREATE TABLE aliases (
|
||||
id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
name text UNIQUE NOT NULL,
|
||||
user_id integer NOT NULL,
|
||||
signature blob not null,
|
||||
|
||||
FOREIGN KEY ( user_id ) REFERENCES allow_list( "id" )
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX aliases_ids ON aliases(id);
|
||||
CREATE UNIQUE INDEX aliases_names ON aliases(name);
|
||||
|
||||
-- +migrate Down
|
||||
DROP TABLE aliases;
|
||||
|
||||
DROP INDEX aliases_ids;
|
||||
DROP INDEX aliases_names;
|
File diff suppressed because it is too large
Load Diff
|
@ -23,7 +23,7 @@ import (
|
|||
|
||||
// AllowList is an object representing the database table.
|
||||
type AllowList struct {
|
||||
ID int64 `boil:"id" json:"id" toml:"id" yaml:"id"`
|
||||
ID int64 `boil:"id" json:"id" toml:"id" yaml:"id"`
|
||||
PubKey roomdb.DBFeedRef `boil:"pub_key" json:"pub_key" toml:"pub_key" yaml:"pub_key"`
|
||||
|
||||
R *allowListR `boil:"-" json:"-" toml:"-" yaml:"-"`
|
||||
|
@ -40,64 +40,45 @@ var AllowListColumns = struct {
|
|||
|
||||
// Generated where
|
||||
|
||||
type whereHelperint64 struct{ field string }
|
||||
type whereHelperroomdb_DBFeedRef struct{ field string }
|
||||
|
||||
func (w whereHelperint64) EQ(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.EQ, x) }
|
||||
func (w whereHelperint64) NEQ(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.NEQ, x) }
|
||||
func (w whereHelperint64) LT(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LT, x) }
|
||||
func (w whereHelperint64) LTE(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LTE, x) }
|
||||
func (w whereHelperint64) GT(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GT, x) }
|
||||
func (w whereHelperint64) GTE(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GTE, x) }
|
||||
func (w whereHelperint64) IN(slice []int64) qm.QueryMod {
|
||||
values := make([]interface{}, 0, len(slice))
|
||||
for _, value := range slice {
|
||||
values = append(values, value)
|
||||
}
|
||||
return qm.WhereIn(fmt.Sprintf("%s IN ?", w.field), values...)
|
||||
}
|
||||
func (w whereHelperint64) NIN(slice []int64) qm.QueryMod {
|
||||
values := make([]interface{}, 0, len(slice))
|
||||
for _, value := range slice {
|
||||
values = append(values, value)
|
||||
}
|
||||
return qm.WhereNotIn(fmt.Sprintf("%s NOT IN ?", w.field), values...)
|
||||
}
|
||||
|
||||
type whereHelperadmindb_DBFeedRef struct{ field string }
|
||||
|
||||
func (w whereHelperadmindb_DBFeedRef) EQ(x roomdb.DBFeedRef) qm.QueryMod {
|
||||
func (w whereHelperroomdb_DBFeedRef) EQ(x roomdb.DBFeedRef) qm.QueryMod {
|
||||
return qmhelper.Where(w.field, qmhelper.EQ, x)
|
||||
}
|
||||
func (w whereHelperadmindb_DBFeedRef) NEQ(x roomdb.DBFeedRef) qm.QueryMod {
|
||||
func (w whereHelperroomdb_DBFeedRef) NEQ(x roomdb.DBFeedRef) qm.QueryMod {
|
||||
return qmhelper.Where(w.field, qmhelper.NEQ, x)
|
||||
}
|
||||
func (w whereHelperadmindb_DBFeedRef) LT(x roomdb.DBFeedRef) qm.QueryMod {
|
||||
func (w whereHelperroomdb_DBFeedRef) LT(x roomdb.DBFeedRef) qm.QueryMod {
|
||||
return qmhelper.Where(w.field, qmhelper.LT, x)
|
||||
}
|
||||
func (w whereHelperadmindb_DBFeedRef) LTE(x roomdb.DBFeedRef) qm.QueryMod {
|
||||
func (w whereHelperroomdb_DBFeedRef) LTE(x roomdb.DBFeedRef) qm.QueryMod {
|
||||
return qmhelper.Where(w.field, qmhelper.LTE, x)
|
||||
}
|
||||
func (w whereHelperadmindb_DBFeedRef) GT(x roomdb.DBFeedRef) qm.QueryMod {
|
||||
func (w whereHelperroomdb_DBFeedRef) GT(x roomdb.DBFeedRef) qm.QueryMod {
|
||||
return qmhelper.Where(w.field, qmhelper.GT, x)
|
||||
}
|
||||
func (w whereHelperadmindb_DBFeedRef) GTE(x roomdb.DBFeedRef) qm.QueryMod {
|
||||
func (w whereHelperroomdb_DBFeedRef) GTE(x roomdb.DBFeedRef) qm.QueryMod {
|
||||
return qmhelper.Where(w.field, qmhelper.GTE, x)
|
||||
}
|
||||
|
||||
var AllowListWhere = struct {
|
||||
ID whereHelperint64
|
||||
PubKey whereHelperadmindb_DBFeedRef
|
||||
PubKey whereHelperroomdb_DBFeedRef
|
||||
}{
|
||||
ID: whereHelperint64{field: "\"allow_list\".\"id\""},
|
||||
PubKey: whereHelperadmindb_DBFeedRef{field: "\"allow_list\".\"pub_key\""},
|
||||
PubKey: whereHelperroomdb_DBFeedRef{field: "\"allow_list\".\"pub_key\""},
|
||||
}
|
||||
|
||||
// AllowListRels is where relationship names are stored.
|
||||
var AllowListRels = struct {
|
||||
}{}
|
||||
UserAliases string
|
||||
}{
|
||||
UserAliases: "UserAliases",
|
||||
}
|
||||
|
||||
// allowListR is where relationships are stored.
|
||||
type allowListR struct {
|
||||
UserAliases AliasSlice `boil:"UserAliases" json:"UserAliases" toml:"UserAliases" yaml:"UserAliases"`
|
||||
}
|
||||
|
||||
// NewStruct creates a new relationship struct
|
||||
|
@ -390,6 +371,178 @@ func (q allowListQuery) Exists(ctx context.Context, exec boil.ContextExecutor) (
|
|||
return count > 0, nil
|
||||
}
|
||||
|
||||
// UserAliases retrieves all the alias's Aliases with an executor via user_id column.
|
||||
func (o *AllowList) UserAliases(mods ...qm.QueryMod) aliasQuery {
|
||||
var queryMods []qm.QueryMod
|
||||
if len(mods) != 0 {
|
||||
queryMods = append(queryMods, mods...)
|
||||
}
|
||||
|
||||
queryMods = append(queryMods,
|
||||
qm.Where("\"aliases\".\"user_id\"=?", o.ID),
|
||||
)
|
||||
|
||||
query := Aliases(queryMods...)
|
||||
queries.SetFrom(query.Query, "\"aliases\"")
|
||||
|
||||
if len(queries.GetSelect(query.Query)) == 0 {
|
||||
queries.SetSelect(query.Query, []string{"\"aliases\".*"})
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
// LoadUserAliases allows an eager lookup of values, cached into the
|
||||
// loaded structs of the objects. This is for a 1-M or N-M relationship.
|
||||
func (allowListL) LoadUserAliases(ctx context.Context, e boil.ContextExecutor, singular bool, maybeAllowList interface{}, mods queries.Applicator) error {
|
||||
var slice []*AllowList
|
||||
var object *AllowList
|
||||
|
||||
if singular {
|
||||
object = maybeAllowList.(*AllowList)
|
||||
} else {
|
||||
slice = *maybeAllowList.(*[]*AllowList)
|
||||
}
|
||||
|
||||
args := make([]interface{}, 0, 1)
|
||||
if singular {
|
||||
if object.R == nil {
|
||||
object.R = &allowListR{}
|
||||
}
|
||||
args = append(args, object.ID)
|
||||
} else {
|
||||
Outer:
|
||||
for _, obj := range slice {
|
||||
if obj.R == nil {
|
||||
obj.R = &allowListR{}
|
||||
}
|
||||
|
||||
for _, a := range args {
|
||||
if a == obj.ID {
|
||||
continue Outer
|
||||
}
|
||||
}
|
||||
|
||||
args = append(args, obj.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
query := NewQuery(
|
||||
qm.From(`aliases`),
|
||||
qm.WhereIn(`aliases.user_id in ?`, args...),
|
||||
)
|
||||
if mods != nil {
|
||||
mods.Apply(query)
|
||||
}
|
||||
|
||||
results, err := query.QueryContext(ctx, e)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to eager load aliases")
|
||||
}
|
||||
|
||||
var resultSlice []*Alias
|
||||
if err = queries.Bind(results, &resultSlice); err != nil {
|
||||
return errors.Wrap(err, "failed to bind eager loaded slice aliases")
|
||||
}
|
||||
|
||||
if err = results.Close(); err != nil {
|
||||
return errors.Wrap(err, "failed to close results in eager load on aliases")
|
||||
}
|
||||
if err = results.Err(); err != nil {
|
||||
return errors.Wrap(err, "error occurred during iteration of eager loaded relations for aliases")
|
||||
}
|
||||
|
||||
if len(aliasAfterSelectHooks) != 0 {
|
||||
for _, obj := range resultSlice {
|
||||
if err := obj.doAfterSelectHooks(ctx, e); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if singular {
|
||||
object.R.UserAliases = resultSlice
|
||||
for _, foreign := range resultSlice {
|
||||
if foreign.R == nil {
|
||||
foreign.R = &aliasR{}
|
||||
}
|
||||
foreign.R.User = object
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, foreign := range resultSlice {
|
||||
for _, local := range slice {
|
||||
if local.ID == foreign.UserID {
|
||||
local.R.UserAliases = append(local.R.UserAliases, foreign)
|
||||
if foreign.R == nil {
|
||||
foreign.R = &aliasR{}
|
||||
}
|
||||
foreign.R.User = local
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddUserAliases adds the given related objects to the existing relationships
|
||||
// of the allow_list, optionally inserting them as new records.
|
||||
// Appends related to o.R.UserAliases.
|
||||
// Sets related.R.User appropriately.
|
||||
func (o *AllowList) AddUserAliases(ctx context.Context, exec boil.ContextExecutor, insert bool, related ...*Alias) error {
|
||||
var err error
|
||||
for _, rel := range related {
|
||||
if insert {
|
||||
rel.UserID = o.ID
|
||||
if err = rel.Insert(ctx, exec, boil.Infer()); err != nil {
|
||||
return errors.Wrap(err, "failed to insert into foreign table")
|
||||
}
|
||||
} else {
|
||||
updateQuery := fmt.Sprintf(
|
||||
"UPDATE \"aliases\" SET %s WHERE %s",
|
||||
strmangle.SetParamNames("\"", "\"", 0, []string{"user_id"}),
|
||||
strmangle.WhereClause("\"", "\"", 0, aliasPrimaryKeyColumns),
|
||||
)
|
||||
values := []interface{}{o.ID, rel.ID}
|
||||
|
||||
if boil.IsDebug(ctx) {
|
||||
writer := boil.DebugWriterFrom(ctx)
|
||||
fmt.Fprintln(writer, updateQuery)
|
||||
fmt.Fprintln(writer, values)
|
||||
}
|
||||
if _, err = exec.ExecContext(ctx, updateQuery, values...); err != nil {
|
||||
return errors.Wrap(err, "failed to update foreign table")
|
||||
}
|
||||
|
||||
rel.UserID = o.ID
|
||||
}
|
||||
}
|
||||
|
||||
if o.R == nil {
|
||||
o.R = &allowListR{
|
||||
UserAliases: related,
|
||||
}
|
||||
} else {
|
||||
o.R.UserAliases = append(o.R.UserAliases, related...)
|
||||
}
|
||||
|
||||
for _, rel := range related {
|
||||
if rel.R == nil {
|
||||
rel.R = &aliasR{
|
||||
User: o,
|
||||
}
|
||||
} else {
|
||||
rel.R.User = o
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllowLists retrieves all the records using an executor.
|
||||
func AllowLists(mods ...qm.QueryMod) allowListQuery {
|
||||
mods = append(mods, qm.From("\"allow_list\""))
|
||||
|
|
|
@ -42,38 +42,6 @@ var AuthFallbackColumns = struct {
|
|||
|
||||
// Generated where
|
||||
|
||||
type whereHelperstring struct{ field string }
|
||||
|
||||
func (w whereHelperstring) EQ(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.EQ, x) }
|
||||
func (w whereHelperstring) NEQ(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.NEQ, x) }
|
||||
func (w whereHelperstring) LT(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LT, x) }
|
||||
func (w whereHelperstring) LTE(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LTE, x) }
|
||||
func (w whereHelperstring) GT(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GT, x) }
|
||||
func (w whereHelperstring) GTE(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GTE, x) }
|
||||
func (w whereHelperstring) IN(slice []string) qm.QueryMod {
|
||||
values := make([]interface{}, 0, len(slice))
|
||||
for _, value := range slice {
|
||||
values = append(values, value)
|
||||
}
|
||||
return qm.WhereIn(fmt.Sprintf("%s IN ?", w.field), values...)
|
||||
}
|
||||
func (w whereHelperstring) NIN(slice []string) qm.QueryMod {
|
||||
values := make([]interface{}, 0, len(slice))
|
||||
for _, value := range slice {
|
||||
values = append(values, value)
|
||||
}
|
||||
return qm.WhereNotIn(fmt.Sprintf("%s NOT IN ?", w.field), values...)
|
||||
}
|
||||
|
||||
type whereHelper__byte struct{ field string }
|
||||
|
||||
func (w whereHelper__byte) EQ(x []byte) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.EQ, x) }
|
||||
func (w whereHelper__byte) NEQ(x []byte) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.NEQ, x) }
|
||||
func (w whereHelper__byte) LT(x []byte) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LT, x) }
|
||||
func (w whereHelper__byte) LTE(x []byte) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LTE, x) }
|
||||
func (w whereHelper__byte) GT(x []byte) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GT, x) }
|
||||
func (w whereHelper__byte) GTE(x []byte) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GTE, x) }
|
||||
|
||||
var AuthFallbackWhere = struct {
|
||||
ID whereHelperint64
|
||||
Name whereHelperstring
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package models
|
||||
|
||||
var TableNames = struct {
|
||||
Aliases string
|
||||
AllowList string
|
||||
AuthFallback string
|
||||
Invites string
|
||||
|
@ -11,6 +12,7 @@ var TableNames = struct {
|
|||
PinNotices string
|
||||
Pins string
|
||||
}{
|
||||
Aliases: "aliases",
|
||||
AllowList: "allow_list",
|
||||
AuthFallback: "auth_fallback",
|
||||
Invites: "invites",
|
||||
|
|
|
@ -26,8 +26,8 @@ import (
|
|||
|
||||
migrate "github.com/rubenv/sql-migrate"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
)
|
||||
|
||||
type Database struct {
|
||||
|
|
|
@ -40,8 +40,8 @@ func (al AllowList) add(ctx context.Context, tx *sql.Tx, a refs.FeedRef) error {
|
|||
}
|
||||
|
||||
var entry models.AllowList
|
||||
|
||||
entry.PubKey.FeedRef = a
|
||||
|
||||
err := entry.Insert(ctx, tx, boil.Whitelist("pub_key"))
|
||||
if err != nil {
|
||||
var sqlErr sqlite3.Error
|
||||
|
@ -99,7 +99,6 @@ func (al AllowList) List(ctx context.Context) (roomdb.ListEntries, error) {
|
|||
|
||||
var asRefs = make(roomdb.ListEntries, len(all))
|
||||
for i, allowed := range all {
|
||||
|
||||
asRefs[i] = roomdb.ListEntry{
|
||||
ID: allowed.ID,
|
||||
PubKey: allowed.PubKey.FeedRef,
|
||||
|
|
|
@ -14,6 +14,17 @@ import (
|
|||
// ErrNotFound is returned by the admin db if an object couldn't be found.
|
||||
var ErrNotFound = errors.New("roomdb: object not found")
|
||||
|
||||
// Alias is how the roomdb stores an alias.
|
||||
type Alias struct {
|
||||
ID int64
|
||||
|
||||
Name string // or "alias string" as the docs call it
|
||||
|
||||
Feed refs.FeedRef // the ssb identity that belongs to the user
|
||||
|
||||
Signature []byte
|
||||
}
|
||||
|
||||
// User holds all the information an authenticated user of the site has.
|
||||
type User struct {
|
||||
ID int64
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package roomsrv
|
||||
|
||||
import (
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
muxrpc "go.cryptoscope.co/muxrpc/v2"
|
||||
"go.cryptoscope.co/muxrpc/v2/typemux"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/muxrpc/handlers/alias"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/muxrpc/handlers/tunnel/server"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/muxrpc/handlers/whoami"
|
||||
)
|
||||
|
||||
// instantiate and register the muxrpc handlers
|
||||
func (s *Server) initHandlers(aliasDB roomdb.AliasService) {
|
||||
// inistaniate handler packages
|
||||
whoami := whoami.New(s.Whoami())
|
||||
|
||||
tunnelHandler := server.New(
|
||||
kitlog.With(s.logger, "unit", "tunnel"),
|
||||
s.Whoami(),
|
||||
s.StateManager,
|
||||
)
|
||||
|
||||
aliasHandler := alias.New(
|
||||
kitlog.With(s.logger, "unit", "aliases"),
|
||||
s.Whoami(),
|
||||
aliasDB,
|
||||
)
|
||||
|
||||
// register muxrpc commands
|
||||
registries := []typemux.HandlerMux{s.public, s.master}
|
||||
|
||||
for _, mux := range registries {
|
||||
mux.RegisterAsync(muxrpc.Method{"manifest"}, manifest)
|
||||
mux.RegisterAsync(muxrpc.Method{"whoami"}, whoami)
|
||||
|
||||
// register tunnel.connect etc twice (as tunnel.* and room.*)
|
||||
var method = muxrpc.Method{"tunnel"}
|
||||
tunnelHandler.Register(mux, method)
|
||||
|
||||
method = muxrpc.Method{"room"}
|
||||
tunnelHandler.Register(mux, method)
|
||||
|
||||
mux.RegisterAsync(append(method, "registerAlias"), typemux.AsyncFunc(aliasHandler.Register))
|
||||
mux.RegisterAsync(append(method, "revokeAlias"), typemux.AsyncFunc(aliasHandler.Revoke))
|
||||
}
|
||||
}
|
|
@ -6,15 +6,13 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
"go.cryptoscope.co/muxrpc/v2"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/network"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/muxrpc/handlers/tunnel/server"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/muxrpc/handlers/whoami"
|
||||
)
|
||||
|
||||
// opens the shs listener for TCP connections
|
||||
func (s *Server) initNetwork() error {
|
||||
// muxrpc handler creation and authoratization decider
|
||||
mkHandler := func(conn net.Conn) (muxrpc.Handler, error) {
|
||||
|
@ -27,32 +25,16 @@ func (s *Server) initNetwork() error {
|
|||
}
|
||||
|
||||
if s.keyPair.Feed.Equal(remote) {
|
||||
return s.master.MakeHandler(conn)
|
||||
return &s.master, nil
|
||||
}
|
||||
|
||||
if s.authorizer.HasFeed(s.rootCtx, *remote) {
|
||||
return s.public.MakeHandler(conn)
|
||||
return &s.public, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("not authorized")
|
||||
}
|
||||
|
||||
// whoami
|
||||
whoami := whoami.New(kitlog.With(s.logger, "unit", "whoami"), s.Whoami())
|
||||
s.public.Register(whoami)
|
||||
s.master.Register(whoami)
|
||||
|
||||
s.master.Register(manifestPlug)
|
||||
|
||||
// s.master.Register(replicate.NewPlug(s.Users))
|
||||
|
||||
tunnelPlug := server.New(
|
||||
kitlog.With(s.logger, "unit", "tunnel"),
|
||||
s.Whoami(),
|
||||
s.StateManager,
|
||||
)
|
||||
s.public.Register(tunnelPlug)
|
||||
|
||||
// tcp+shs
|
||||
opts := network.Options{
|
||||
Logger: s.logger,
|
||||
|
|
|
@ -25,6 +25,7 @@ func WithUNIXSocket(yes bool) Option {
|
|||
}
|
||||
}
|
||||
|
||||
// creates the UNIX socket file listener for local usage
|
||||
func (s *Server) initUnixSock() error {
|
||||
// this races because roomsrv might not be done with init yet
|
||||
// TODO: refactor network peer code and make unixsock implement that (those will be inited late anyway)
|
||||
|
@ -53,6 +54,7 @@ func (s *Server) initUnixSock() error {
|
|||
|
||||
go func() {
|
||||
|
||||
acceptLoop:
|
||||
for {
|
||||
c, err := uxLis.Accept()
|
||||
if err != nil {
|
||||
|
@ -77,7 +79,7 @@ func (s *Server) initUnixSock() error {
|
|||
if err != nil {
|
||||
level.Warn(s.logger).Log("err", err)
|
||||
c.Close()
|
||||
continue
|
||||
continue acceptLoop
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,13 +88,7 @@ func (s *Server) initUnixSock() error {
|
|||
|
||||
pkr := muxrpc.NewPacker(conn)
|
||||
|
||||
h, err := s.master.MakeHandler(conn)
|
||||
if err != nil {
|
||||
level.Warn(s.logger).Log("event", "unix sock make handler", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
edp := muxrpc.Handle(pkr, h,
|
||||
edp := muxrpc.Handle(pkr, &s.master,
|
||||
muxrpc.WithContext(s.rootCtx),
|
||||
muxrpc.WithLogger(kitlog.NewNopLogger()),
|
||||
)
|
||||
|
|
|
@ -6,32 +6,14 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"go.cryptoscope.co/muxrpc/v2"
|
||||
)
|
||||
|
||||
type namedPlugin struct {
|
||||
h muxrpc.Handler
|
||||
name string
|
||||
}
|
||||
|
||||
func (np namedPlugin) Name() string { return np.name }
|
||||
func (np namedPlugin) Method() muxrpc.Method { return muxrpc.Method{np.name} }
|
||||
func (np namedPlugin) Handler() muxrpc.Handler { return np.h }
|
||||
func (np namedPlugin) Authorize(net.Conn) bool { return true }
|
||||
|
||||
type manifestHandler string
|
||||
|
||||
func (manifestHandler) Handled(m muxrpc.Method) bool { return m.String() == "manifest" }
|
||||
|
||||
func (manifestHandler) HandleConnect(context.Context, muxrpc.Endpoint) {}
|
||||
|
||||
func (h manifestHandler) HandleCall(ctx context.Context, req *muxrpc.Request) {
|
||||
err := req.Return(ctx, json.RawMessage(h))
|
||||
if err != nil {
|
||||
fmt.Println("manifest err", err)
|
||||
}
|
||||
func (h manifestHandler) HandleAsync(ctx context.Context, req *muxrpc.Request) (interface{}, error) {
|
||||
return json.RawMessage(h), nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -50,6 +32,18 @@ const manifest manifestHandler = `
|
|||
|
||||
"whoami":"async",
|
||||
|
||||
"room": {
|
||||
"registerAlias": "async",
|
||||
"revokeAlias": "async",
|
||||
|
||||
"announce": "sync",
|
||||
"leave": "sync",
|
||||
"connect": "duplex",
|
||||
"endpoints": "source",
|
||||
"isRoom": "async",
|
||||
"ping": "sync"
|
||||
},
|
||||
|
||||
"tunnel": {
|
||||
"announce": "sync",
|
||||
"leave": "sync",
|
||||
|
@ -59,8 +53,3 @@ const manifest manifestHandler = `
|
|||
"ping": "sync"
|
||||
}
|
||||
}`
|
||||
|
||||
var manifestPlug = namedPlugin{
|
||||
h: manifest,
|
||||
name: "manifest",
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package roomsrv implements the muxrpc server for all the room related code.
|
||||
// It ties the muxrpc/handlers packages and network listeners together.
|
||||
package roomsrv
|
||||
|
||||
import (
|
||||
|
@ -14,11 +16,11 @@ import (
|
|||
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
"github.com/go-kit/kit/log/level"
|
||||
"go.cryptoscope.co/muxrpc/v2/typemux"
|
||||
"go.cryptoscope.co/netwrap"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/maybemod/keys"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/maybemod/multicloser"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/maybemuxrpc"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/network"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
|
@ -53,24 +55,31 @@ type Server struct {
|
|||
preSecureWrappers []netwrap.ConnWrapper
|
||||
postSecureWrappers []netwrap.ConnWrapper
|
||||
|
||||
public maybemuxrpc.PluginManager
|
||||
master maybemuxrpc.PluginManager
|
||||
public typemux.HandlerMux
|
||||
master typemux.HandlerMux
|
||||
|
||||
authorizer roomdb.AllowListService
|
||||
|
||||
StateManager *roomstate.Manager
|
||||
|
||||
AllowList roomdb.AllowListService
|
||||
Aliases roomdb.AliasService
|
||||
}
|
||||
|
||||
func (s Server) Whoami() refs.FeedRef {
|
||||
return s.keyPair.Feed
|
||||
}
|
||||
|
||||
func New(allow roomdb.AllowListService, opts ...Option) (*Server, error) {
|
||||
func New(
|
||||
allowdb roomdb.AllowListService,
|
||||
aliasdb roomdb.AliasService,
|
||||
opts ...Option,
|
||||
) (*Server, error) {
|
||||
var s Server
|
||||
s.authorizer = allow
|
||||
s.authorizer = allowdb
|
||||
|
||||
s.public = maybemuxrpc.NewPluginManager()
|
||||
s.master = maybemuxrpc.NewPluginManager()
|
||||
s.AllowList = allowdb
|
||||
s.Aliases = aliasdb
|
||||
|
||||
for i, opt := range opts {
|
||||
err := opt(&s)
|
||||
|
@ -110,6 +119,9 @@ func New(allow roomdb.AllowListService, opts ...Option) (*Server, error) {
|
|||
s.logger = logger
|
||||
}
|
||||
|
||||
s.public = typemux.New(kitlog.With(s.logger, "mux", "public"))
|
||||
s.master = typemux.New(kitlog.With(s.logger, "mux", "master"))
|
||||
|
||||
if s.rootCtx == nil {
|
||||
s.rootCtx, s.Shutdown = context.WithCancel(context.Background())
|
||||
}
|
||||
|
@ -126,6 +138,8 @@ func New(allow roomdb.AllowListService, opts ...Option) (*Server, error) {
|
|||
|
||||
s.StateManager = roomstate.NewManager(s.rootCtx, s.logger)
|
||||
|
||||
s.initHandlers(aliasdb)
|
||||
|
||||
if err := s.initNetwork(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/csrf"
|
||||
"go.mindeco.de/http/render"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
weberrors "github.com/ssb-ngi-pointer/go-ssb-room/web/errors"
|
||||
)
|
||||
|
||||
// aliasesHandler implements the managment endpoints for aliases (list and revoke),
|
||||
// does light validation of the web arguments and passes them through to the roomdb.
|
||||
type aliasesHandler struct {
|
||||
r *render.Renderer
|
||||
|
||||
db roomdb.AliasService
|
||||
}
|
||||
|
||||
const redirectToAliases = "/admin/aliases"
|
||||
|
||||
func (h aliasesHandler) overview(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
lst, err := h.db.List(req.Context())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Reverse the slice to provide recent-to-oldest results
|
||||
for i, j := 0, len(lst)-1; i < j; i, j = i+1, j-1 {
|
||||
lst[i], lst[j] = lst[j], lst[i]
|
||||
}
|
||||
|
||||
pageData, err := paginate(lst, len(lst), req.URL.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pageData, nil
|
||||
}
|
||||
|
||||
func (h aliasesHandler) revokeConfirm(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
if req.Method != "GET" {
|
||||
return nil, weberrors.ErrBadRequest{Where: "HTTP Method", Details: fmt.Errorf("expected GET request")}
|
||||
}
|
||||
|
||||
id, err := strconv.ParseInt(req.URL.Query().Get("id"), 10, 64)
|
||||
if err != nil {
|
||||
err = weberrors.ErrBadRequest{Where: "ID", Details: err}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entry, err := h.db.GetByID(req.Context(), id)
|
||||
if err != nil {
|
||||
if errors.Is(err, roomdb.ErrNotFound) {
|
||||
http.Redirect(rw, req, redirectToAliases, http.StatusFound)
|
||||
return nil, ErrRedirected
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"Entry": entry,
|
||||
csrf.TemplateTag: csrf.TemplateField(req),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h aliasesHandler) revoke(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != "POST" {
|
||||
err := weberrors.ErrBadRequest{Where: "HTTP Method", Details: fmt.Errorf("expected POST request")}
|
||||
h.r.Error(rw, req, http.StatusMethodNotAllowed, err)
|
||||
return
|
||||
}
|
||||
|
||||
err := req.ParseForm()
|
||||
if err != nil {
|
||||
err = weberrors.ErrBadRequest{Where: "Form data", Details: err}
|
||||
http.Redirect(rw, req, redirectToAliases, http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
status := http.StatusFound
|
||||
err = h.db.Revoke(req.Context(), req.FormValue("name"))
|
||||
if err != nil {
|
||||
if !errors.Is(err, roomdb.ErrNotFound) {
|
||||
|
||||
h.r.Error(rw, req, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
status = http.StatusNotFound
|
||||
}
|
||||
|
||||
http.Redirect(rw, req, redirectToAliases, status)
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
)
|
||||
|
||||
func TestAliasesOverview(t *testing.T) {
|
||||
ts := newSession(t)
|
||||
a := assert.New(t)
|
||||
|
||||
lst := []roomdb.Alias{
|
||||
{ID: 1, Name: "alice", Feed: refs.FeedRef{ID: bytes.Repeat([]byte{0}, 32), Algo: "fake"}},
|
||||
{ID: 2, Name: "bob", Feed: refs.FeedRef{ID: bytes.Repeat([]byte("1312"), 8), Algo: "test"}},
|
||||
{ID: 3, Name: "cleo", Feed: refs.FeedRef{ID: bytes.Repeat([]byte("acab"), 8), Algo: "true"}},
|
||||
}
|
||||
ts.Aliases.ListReturns(lst, nil)
|
||||
|
||||
html, resp := ts.Client.GetHTML("/aliases")
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||
|
||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||
{"#welcome", "AdminAliasesWelcome"},
|
||||
{"title", "AdminAliasesTitle"},
|
||||
{"#aliasCount", "ListCountPlural"},
|
||||
})
|
||||
|
||||
a.EqualValues(html.Find("#theList li").Length(), 3)
|
||||
|
||||
lst = []roomdb.Alias{
|
||||
{ID: 666, Name: "dave", Feed: refs.FeedRef{ID: bytes.Repeat([]byte{1}, 32), Algo: "one"}},
|
||||
}
|
||||
ts.Aliases.ListReturns(lst, nil)
|
||||
|
||||
html, resp = ts.Client.GetHTML("/aliases")
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||
|
||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||
{"#welcome", "AdminAliasesWelcome"},
|
||||
{"title", "AdminAliasesTitle"},
|
||||
{"#aliasCount", "ListCountSingular"},
|
||||
})
|
||||
|
||||
elems := html.Find("#theList li")
|
||||
a.EqualValues(elems.Length(), 1)
|
||||
|
||||
// check for link to Revoke confirm link
|
||||
link, yes := elems.ContentsFiltered("a").Attr("href")
|
||||
a.True(yes, "a-tag has href attribute")
|
||||
a.Equal("/admin/aliases/revoke/confirm?id=666", link)
|
||||
}
|
||||
|
||||
func TestAliasesRevokeConfirmation(t *testing.T) {
|
||||
ts := newSession(t)
|
||||
a := assert.New(t)
|
||||
|
||||
testKey, err := refs.ParseFeedRef("@x7iOLUcq3o+sjGeAnipvWeGzfuYgrXl8L4LYlxIhwDc=.ed25519")
|
||||
a.NoError(err)
|
||||
testEntry := roomdb.Alias{ID: 666, Name: "the-test-name", Feed: *testKey}
|
||||
ts.Aliases.GetByIDReturns(testEntry, nil)
|
||||
|
||||
urlTo := web.NewURLTo(ts.Router)
|
||||
urlRevokeConfirm := urlTo(router.AdminAliasesRevokeConfirm, "id", 3)
|
||||
|
||||
html, resp := ts.Client.GetHTML(urlRevokeConfirm.String())
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||
|
||||
a.Equal(testKey.Ref(), html.Find("pre#verify").Text(), "has the key for verification")
|
||||
|
||||
form := html.Find("form#confirm")
|
||||
|
||||
method, ok := form.Attr("method")
|
||||
a.True(ok, "form has method set")
|
||||
a.Equal("POST", method)
|
||||
|
||||
action, ok := form.Attr("action")
|
||||
a.True(ok, "form has action set")
|
||||
|
||||
addURL, err := ts.Router.Get(router.AdminAliasesRevoke).URL()
|
||||
a.NoError(err)
|
||||
|
||||
a.Equal(addURL.String(), action)
|
||||
|
||||
webassert.InputsInForm(t, form, []webassert.InputElement{
|
||||
{Name: "name", Type: "hidden", Value: testEntry.Name},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAliasesRevoke(t *testing.T) {
|
||||
ts := newSession(t)
|
||||
a := assert.New(t)
|
||||
|
||||
urlTo := web.NewURLTo(ts.Router)
|
||||
urlRevoke := urlTo(router.AdminAliasesRevoke)
|
||||
|
||||
ts.Aliases.RevokeReturns(nil)
|
||||
|
||||
addVals := url.Values{"name": []string{"the-name"}}
|
||||
rec := ts.Client.PostForm(urlRevoke.String(), addVals)
|
||||
a.Equal(http.StatusFound, rec.Code)
|
||||
|
||||
a.Equal(1, ts.Aliases.RevokeCallCount())
|
||||
_, theName := ts.Aliases.RevokeArgsForCall(0)
|
||||
a.EqualValues("the-name", theName)
|
||||
|
||||
// now for unknown ID
|
||||
ts.Aliases.RevokeReturns(roomdb.ErrNotFound)
|
||||
addVals = url.Values{"name": []string{"nope"}}
|
||||
rec = ts.Client.PostForm(urlRevoke.String(), addVals)
|
||||
a.Equal(http.StatusNotFound, rec.Code)
|
||||
//TODO: update redirect code with flash errors
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
|
|
|
@ -27,6 +27,7 @@ type testSession struct {
|
|||
Client *tester.Tester
|
||||
Router *mux.Router
|
||||
|
||||
Aliases *mockdb.FakeAliasService
|
||||
AllowListDB *mockdb.FakeAllowListService
|
||||
PinnedDB *mockdb.FakePinnedNoticesService
|
||||
NoticeDB *mockdb.FakeNoticesService
|
||||
|
@ -43,6 +44,7 @@ func newSession(t *testing.T) *testSession {
|
|||
var ts testSession
|
||||
|
||||
// fake dbs
|
||||
ts.Aliases = new(mockdb.FakeAliasService)
|
||||
ts.AllowListDB = new(mockdb.FakeAllowListService)
|
||||
ts.PinnedDB = new(mockdb.FakePinnedNoticesService)
|
||||
ts.NoticeDB = new(mockdb.FakeNoticesService)
|
||||
|
@ -97,6 +99,7 @@ func newSession(t *testing.T) *testSession {
|
|||
r,
|
||||
ts.RoomState,
|
||||
Databases{
|
||||
Aliases: ts.Aliases,
|
||||
AllowList: ts.AllowListDB,
|
||||
Invites: ts.InvitesDB,
|
||||
Notices: ts.NoticeDB,
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package admin implements the dashboard for admins and moderators to change and control aspects of the room.
|
||||
// Including aliases, allow/deny list managment, invites and settings of the room.
|
||||
package admin
|
||||
|
||||
import (
|
||||
|
@ -18,10 +20,14 @@ import (
|
|||
"github.com/ssb-ngi-pointer/go-ssb-room/roomstate"
|
||||
)
|
||||
|
||||
// HTMLTemplates define the list of files the template system should load.
|
||||
var HTMLTemplates = []string{
|
||||
"admin/dashboard.tmpl",
|
||||
"admin/menu.tmpl",
|
||||
|
||||
"admin/aliases.tmpl",
|
||||
"admin/aliases-revoke-confirm.tmpl",
|
||||
|
||||
"admin/allow-list.tmpl",
|
||||
"admin/allow-list-remove-confirm.tmpl",
|
||||
|
||||
|
@ -34,6 +40,7 @@ var HTMLTemplates = []string{
|
|||
|
||||
// Databases is an option struct that encapsualtes the required database services
|
||||
type Databases struct {
|
||||
Aliases roomdb.AliasService
|
||||
AllowList roomdb.AllowListService
|
||||
Invites roomdb.InviteService
|
||||
Notices roomdb.NoticesService
|
||||
|
@ -62,14 +69,22 @@ func Handler(
|
|||
return map[string]interface{}{}, nil
|
||||
}))
|
||||
|
||||
var ah = allowListHandler{
|
||||
var ah = aliasesHandler{
|
||||
r: r,
|
||||
db: dbs.Aliases,
|
||||
}
|
||||
mux.HandleFunc("/aliases", r.HTML("admin/aliases.tmpl", ah.overview))
|
||||
mux.HandleFunc("/aliases/revoke/confirm", r.HTML("admin/aliases-revoke-confirm.tmpl", ah.revokeConfirm))
|
||||
mux.HandleFunc("/aliases/revoke", ah.revoke)
|
||||
|
||||
var mh = allowListHandler{
|
||||
r: r,
|
||||
al: dbs.AllowList,
|
||||
}
|
||||
mux.HandleFunc("/members", r.HTML("admin/allow-list.tmpl", ah.overview))
|
||||
mux.HandleFunc("/members/add", ah.add)
|
||||
mux.HandleFunc("/members/remove/confirm", r.HTML("admin/allow-list-remove-confirm.tmpl", ah.removeConfirm))
|
||||
mux.HandleFunc("/members/remove", ah.remove)
|
||||
mux.HandleFunc("/members", r.HTML("admin/allow-list.tmpl", mh.overview))
|
||||
mux.HandleFunc("/members/add", mh.add)
|
||||
mux.HandleFunc("/members/remove/confirm", r.HTML("admin/allow-list-remove-confirm.tmpl", mh.removeConfirm))
|
||||
mux.HandleFunc("/members/remove", mh.remove)
|
||||
|
||||
var ih = invitesHandler{
|
||||
r: r,
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"go.mindeco.de/http/render"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/aliases"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
)
|
||||
|
||||
// aliasHandler implements the public resolve endpoint for HTML and JSON requests.
|
||||
type aliasHandler struct {
|
||||
r *render.Renderer
|
||||
|
||||
db roomdb.AliasService
|
||||
|
||||
muxrpcHostAndPort string
|
||||
roomID refs.FeedRef
|
||||
}
|
||||
|
||||
func (a aliasHandler) resolve(rw http.ResponseWriter, req *http.Request) {
|
||||
respEncoding := req.URL.Query().Get("encoding")
|
||||
|
||||
var ar aliasResponder
|
||||
switch respEncoding {
|
||||
case "json":
|
||||
ar = newAliasJSONResponder(rw)
|
||||
default:
|
||||
ar = newAliasHTMLResponder(a.r, rw, req)
|
||||
}
|
||||
|
||||
ar.UpdateRoomInfo(a.muxrpcHostAndPort, a.roomID)
|
||||
|
||||
name := mux.Vars(req)["alias"]
|
||||
if name == "" && !aliases.IsValid(name) {
|
||||
ar.SendError(fmt.Errorf("invalid alias"))
|
||||
return
|
||||
}
|
||||
|
||||
alias, err := a.db.Resolve(req.Context(), name)
|
||||
if err != nil {
|
||||
ar.SendError(err)
|
||||
return
|
||||
}
|
||||
|
||||
ar.SendConfirmation(alias)
|
||||
}
|
||||
|
||||
// aliasResponder is supposed to handle different encoding types transparently.
|
||||
// It either sends the signed alias confirmation or an error.
|
||||
type aliasResponder interface {
|
||||
SendConfirmation(roomdb.Alias)
|
||||
SendError(error)
|
||||
|
||||
UpdateRoomInfo(hostAndPort string, roomID refs.FeedRef)
|
||||
}
|
||||
|
||||
// aliasJSONResponse dictates the field names and format of the JSON response for the alias web endpoint
|
||||
type aliasJSONResponse struct {
|
||||
Status string `json:"status"`
|
||||
Address string `json:"address"`
|
||||
RoomID string `json:"roomId"`
|
||||
UserID string `json:"userId"`
|
||||
Alias string `json:"alias"`
|
||||
Signature string `json:"signature"`
|
||||
}
|
||||
|
||||
// handles JSON responses
|
||||
type aliasJSONResponder struct {
|
||||
enc *json.Encoder
|
||||
|
||||
roomID refs.FeedRef
|
||||
multiservAddr string
|
||||
}
|
||||
|
||||
func newAliasJSONResponder(rw http.ResponseWriter) aliasResponder {
|
||||
return &aliasJSONResponder{
|
||||
enc: json.NewEncoder(rw),
|
||||
}
|
||||
}
|
||||
|
||||
func (json *aliasJSONResponder) UpdateRoomInfo(hostAndPort string, roomID refs.FeedRef) {
|
||||
json.roomID = roomID
|
||||
|
||||
roomPubKey := base64.StdEncoding.EncodeToString(roomID.PubKey())
|
||||
json.multiservAddr = fmt.Sprintf("net:%s~shs:%s", hostAndPort, roomPubKey)
|
||||
}
|
||||
|
||||
func (json aliasJSONResponder) SendConfirmation(alias roomdb.Alias) {
|
||||
var resp = aliasJSONResponse{
|
||||
Status: "successful",
|
||||
RoomID: json.roomID.Ref(),
|
||||
Address: json.multiservAddr,
|
||||
Alias: alias.Name,
|
||||
UserID: alias.Feed.Ref(),
|
||||
Signature: base64.StdEncoding.EncodeToString(alias.Signature),
|
||||
}
|
||||
json.enc.Encode(resp)
|
||||
}
|
||||
|
||||
func (json aliasJSONResponder) SendError(err error) {
|
||||
json.enc.Encode(struct {
|
||||
Status string `json:"status"`
|
||||
Error string `json:"error"`
|
||||
}{"error", err.Error()})
|
||||
}
|
||||
|
||||
// handles HTML responses
|
||||
type aliasHTMLResponder struct {
|
||||
renderer *render.Renderer
|
||||
rw http.ResponseWriter
|
||||
req *http.Request
|
||||
|
||||
roomID refs.FeedRef
|
||||
multiservAddr string
|
||||
}
|
||||
|
||||
func newAliasHTMLResponder(r *render.Renderer, rw http.ResponseWriter, req *http.Request) aliasResponder {
|
||||
return &aliasHTMLResponder{
|
||||
renderer: r,
|
||||
rw: rw,
|
||||
req: req,
|
||||
}
|
||||
}
|
||||
|
||||
func (html *aliasHTMLResponder) UpdateRoomInfo(hostAndPort string, roomID refs.FeedRef) {
|
||||
html.roomID = roomID
|
||||
|
||||
roomPubKey := base64.StdEncoding.EncodeToString(roomID.PubKey())
|
||||
html.multiservAddr = fmt.Sprintf("net:%s~shs:%s", hostAndPort, roomPubKey)
|
||||
}
|
||||
|
||||
func (html aliasHTMLResponder) SendConfirmation(alias roomdb.Alias) {
|
||||
err := html.renderer.Render(html.rw, html.req, "aliases-resolved.html", http.StatusOK, struct {
|
||||
Alias roomdb.Alias
|
||||
|
||||
RoomAddr string
|
||||
}{alias, html.multiservAddr})
|
||||
if err != nil {
|
||||
log.Println("alias-resolve render errr:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (html aliasHTMLResponder) SendError(err error) {
|
||||
html.renderer.Error(html.rw, html.req, http.StatusInternalServerError, err)
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
)
|
||||
|
||||
func TestAliasResolve(t *testing.T) {
|
||||
ts := setup(t)
|
||||
|
||||
a := assert.New(t)
|
||||
r := require.New(t)
|
||||
|
||||
var testAlias = roomdb.Alias{
|
||||
ID: 54321,
|
||||
Name: "test-name",
|
||||
Feed: refs.FeedRef{
|
||||
ID: bytes.Repeat([]byte{'F'}, 32),
|
||||
Algo: "test",
|
||||
},
|
||||
Signature: bytes.Repeat([]byte{'S'}, 32),
|
||||
}
|
||||
ts.AliasesDB.ResolveReturns(testAlias, nil)
|
||||
|
||||
// default is HTML
|
||||
htmlURL, err := ts.Router.Get(router.CompleteAliasResolve).URL("alias", testAlias.Name)
|
||||
r.Nil(err)
|
||||
t.Log("resolving", htmlURL.String())
|
||||
html, resp := ts.Client.GetHTML(htmlURL.String())
|
||||
a.Equal(http.StatusOK, resp.Code)
|
||||
|
||||
a.Equal(testAlias.Name, html.Find("title").Text())
|
||||
|
||||
// default is HTML
|
||||
jsonURL, err := ts.Router.Get(router.CompleteAliasResolve).URL("alias", testAlias.Name)
|
||||
r.Nil(err)
|
||||
q := jsonURL.Query()
|
||||
q.Set("encoding", "json")
|
||||
jsonURL.RawQuery = q.Encode()
|
||||
t.Log("resolving", jsonURL.String())
|
||||
resp = ts.Client.GetBody(jsonURL.String())
|
||||
a.Equal(http.StatusOK, resp.Code)
|
||||
|
||||
var ar aliasJSONResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&ar)
|
||||
r.NoError(err)
|
||||
a.Equal(testAlias.Name, ar.Alias)
|
||||
sigData, err := base64.StdEncoding.DecodeString(ar.Signature)
|
||||
r.NoError(err)
|
||||
a.Equal(testAlias.Signature, sigData)
|
||||
a.Equal(testAlias.Feed.Ref(), ar.UserID, "wrong user feed on response")
|
||||
a.Equal(ts.NetworkInfo.RoomID.Ref(), ar.RoomID, "wrong room feed on response")
|
||||
}
|
|
@ -26,7 +26,6 @@ func TestIndex(t *testing.T) {
|
|||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||
{"h1", "Default Notice Title"},
|
||||
{"title", "Default Notice Title"},
|
||||
// {"#nav", "FooBar"},
|
||||
})
|
||||
|
||||
content := html.Find("p").Text()
|
||||
|
|
|
@ -11,16 +11,17 @@ import (
|
|||
"strconv"
|
||||
"time"
|
||||
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
|
||||
"github.com/gorilla/csrf"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/russross/blackfriday/v2"
|
||||
"go.mindeco.de/http/auth"
|
||||
"go.mindeco.de/http/render"
|
||||
"go.mindeco.de/logging"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomstate"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/handlers/admin"
|
||||
|
@ -33,6 +34,7 @@ import (
|
|||
var HTMLTemplates = []string{
|
||||
"landing/index.tmpl",
|
||||
"landing/about.tmpl",
|
||||
"aliases-resolved.html",
|
||||
"invite/accept.tmpl",
|
||||
"invite/consumed.tmpl",
|
||||
"notice/list.tmpl",
|
||||
|
@ -42,6 +44,7 @@ var HTMLTemplates = []string{
|
|||
|
||||
// Databases is an options stuct for the required databases of the web handlers
|
||||
type Databases struct {
|
||||
Aliases roomdb.AliasService
|
||||
AuthWithSSB roomdb.AuthWithSSBService
|
||||
AuthFallback roomdb.AuthFallbackService
|
||||
AllowList roomdb.AllowListService
|
||||
|
@ -55,7 +58,7 @@ type NetworkInfo struct {
|
|||
PortMUXRPC uint
|
||||
PortHTTPS uint // 0 assumes default (443)
|
||||
|
||||
PubKey ed25519.PublicKey
|
||||
RoomID refs.FeedRef
|
||||
|
||||
Domain string
|
||||
}
|
||||
|
@ -97,7 +100,11 @@ func New(
|
|||
}),
|
||||
render.InjectTemplateFunc("current_page_is", func(r *http.Request) interface{} {
|
||||
return func(routeName string) bool {
|
||||
url, err := router.CompleteApp().Get(routeName).URLPath()
|
||||
route := router.CompleteApp().Get(routeName)
|
||||
if route == nil {
|
||||
return false
|
||||
}
|
||||
url, err := route.URLPath()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
@ -226,6 +233,7 @@ func New(
|
|||
r,
|
||||
roomState,
|
||||
admin.Databases{
|
||||
Aliases: dbs.Aliases,
|
||||
AllowList: dbs.AllowList,
|
||||
Invites: dbs.Invites,
|
||||
Notices: dbs.Notices,
|
||||
|
@ -256,10 +264,20 @@ func New(
|
|||
m.Get(router.CompleteNoticeList).Handler(r.HTML("notice/list.tmpl", nh.list))
|
||||
m.Get(router.CompleteNoticeShow).Handler(r.HTML("notice/show.tmpl", nh.show))
|
||||
|
||||
var ah = aliasHandler{
|
||||
r: r,
|
||||
|
||||
db: dbs.Aliases,
|
||||
|
||||
roomID: netInfo.RoomID,
|
||||
muxrpcHostAndPort: fmt.Sprintf("%s:%d", netInfo.Domain, netInfo.PortMUXRPC),
|
||||
}
|
||||
m.Get(router.CompleteAliasResolve).HandlerFunc(ah.resolve)
|
||||
|
||||
var ih = inviteHandler{
|
||||
invites: dbs.Invites,
|
||||
|
||||
roomPubKey: netInfo.PubKey,
|
||||
roomPubKey: netInfo.RoomID.PubKey(),
|
||||
muxrpcHostAndPort: fmt.Sprintf("%s:%d", netInfo.Domain, netInfo.PortMUXRPC),
|
||||
}
|
||||
m.Get(router.CompleteInviteAccept).Handler(r.HTML("invite/accept.tmpl", ih.acceptForm))
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"go.mindeco.de/http/render"
|
||||
"go.mindeco.de/logging"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
|
||||
|
@ -18,8 +17,6 @@ import (
|
|||
)
|
||||
|
||||
type inviteHandler struct {
|
||||
r *render.Renderer
|
||||
|
||||
invites roomdb.InviteService
|
||||
aliases roomdb.AliasService
|
||||
|
||||
|
|
|
@ -226,7 +226,7 @@ func TestInviteConsumeInvite(t *testing.T) {
|
|||
|
||||
// TODO: this is just a cheap stub for actual ssb-uri parsing
|
||||
a.True(strings.HasPrefix(gotRA, "net:localhost:8008~shs:"), "not for the test host: %s", gotRA)
|
||||
a.True(strings.Contains(gotRA, base64.StdEncoding.EncodeToString(ts.NetworkInfo.PubKey)), "public key missing? %s", gotRA)
|
||||
a.True(strings.Contains(gotRA, base64.StdEncoding.EncodeToString(ts.NetworkInfo.RoomID.PubKey())), "public key missing? %s", gotRA)
|
||||
a.True(strings.HasSuffix(gotRA, ":SSB+Room+PSK3TLYC2T86EHQCUHBUHASCASE18JBV24="), "magic suffix missing: %s", gotRA)
|
||||
|
||||
}
|
||||
|
|
|
@ -16,10 +16,11 @@ import (
|
|||
"github.com/gorilla/mux"
|
||||
"go.mindeco.de/http/tester"
|
||||
"go.mindeco.de/logging/logtest"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb/mockdb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomstate"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/i18n"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||
|
@ -33,6 +34,7 @@ type testSession struct {
|
|||
// mocked dbs
|
||||
AuthDB *mockdb.FakeAuthWithSSBService
|
||||
AuthFallbackDB *mockdb.FakeAuthFallbackService
|
||||
AliasesDB *mockdb.FakeAliasService
|
||||
AllowListDB *mockdb.FakeAllowListService
|
||||
InvitesDB *mockdb.FakeInviteService
|
||||
PinnedDB *mockdb.FakePinnedNoticesService
|
||||
|
@ -61,6 +63,7 @@ func setup(t *testing.T) *testSession {
|
|||
|
||||
ts.AuthDB = new(mockdb.FakeAuthWithSSBService)
|
||||
ts.AuthFallbackDB = new(mockdb.FakeAuthFallbackService)
|
||||
ts.AliasesDB = new(mockdb.FakeAliasService)
|
||||
ts.AllowListDB = new(mockdb.FakeAllowListService)
|
||||
ts.InvitesDB = new(mockdb.FakeInviteService)
|
||||
ts.PinnedDB = new(mockdb.FakePinnedNoticesService)
|
||||
|
@ -76,7 +79,10 @@ func setup(t *testing.T) *testSession {
|
|||
PortMUXRPC: 8008,
|
||||
PortHTTPS: 443,
|
||||
|
||||
PubKey: bytes.Repeat([]byte("test"), 8),
|
||||
RoomID: refs.FeedRef{
|
||||
ID: bytes.Repeat([]byte("test"), 8),
|
||||
Algo: refs.RefAlgoFeedSSB1,
|
||||
},
|
||||
}
|
||||
|
||||
log, _ := logtest.KitLogger("complete", t)
|
||||
|
@ -91,6 +97,7 @@ func setup(t *testing.T) *testSession {
|
|||
ts.NetworkInfo,
|
||||
ts.RoomState,
|
||||
Databases{
|
||||
Aliases: ts.AliasesDB,
|
||||
AuthWithSSB: ts.AuthDB,
|
||||
AuthFallback: ts.AuthFallbackDB,
|
||||
AllowList: ts.AllowListDB,
|
||||
|
|
|
@ -18,6 +18,10 @@ AuthSignOut = "Sign out"
|
|||
AdminDashboardWelcome = "Welcome to your dashboard"
|
||||
AdminDashboardTitle = "Room Admin Dashboard"
|
||||
|
||||
AdminAliasesTitle = "Aliases"
|
||||
AdminAliasesWelcome = "Here you can see and revoke the registered aliases of this room."
|
||||
AdminAliasesRevoke = "Revoke"
|
||||
|
||||
AdminAllowListTitle = "Members"
|
||||
AdminAllowListWelcome = "Here you can see all the members of the room and ways to add new ones (by their SSB ID) or remove exising ones."
|
||||
AdminAllowListAdd = "Add"
|
||||
|
@ -84,4 +88,4 @@ other = "There are {{.Count}} items on the List"
|
|||
[AdminRoomCount]
|
||||
description = "The number of people in a room"
|
||||
one = "There is one person in the Room"
|
||||
other = "There are {{.Count}} people in the Room"
|
||||
other = "There are {{.Count}} people in the Room"
|
||||
|
|
|
@ -9,6 +9,10 @@ const (
|
|||
AdminDashboard = "admin:dashboard"
|
||||
AdminMenu = "admin:menu"
|
||||
|
||||
AdminAliasesOverview = "admin:aliases:overview"
|
||||
AdminAliasesRevokeConfirm = "admin:aliases:revoke:confirm"
|
||||
AdminAliasesRevoke = "admin:aliases:revoke"
|
||||
|
||||
AdminAllowListOverview = "admin:allow-list:overview"
|
||||
AdminAllowListAdd = "admin:allow-list:add"
|
||||
AdminAllowListRemoveConfirm = "admin:allow-list:remove:confirm"
|
||||
|
@ -34,6 +38,10 @@ func Admin(m *mux.Router) *mux.Router {
|
|||
m.Path("/dashboard").Methods("GET").Name(AdminDashboard)
|
||||
m.Path("/menu").Methods("GET").Name(AdminMenu)
|
||||
|
||||
m.Path("/aliases").Methods("GET").Name(AdminAliasesOverview)
|
||||
m.Path("/aliases/revoke/confirm").Methods("GET").Name(AdminAliasesRevokeConfirm)
|
||||
m.Path("/aliases/revoke").Methods("POST").Name(AdminAliasesRevoke)
|
||||
|
||||
m.Path("/members").Methods("GET").Name(AdminAllowListOverview)
|
||||
m.Path("/members/add").Methods("POST").Name(AdminAllowListAdd)
|
||||
m.Path("/members/remove/confirm").Methods("GET").Name(AdminAllowListRemoveConfirm)
|
||||
|
|
|
@ -14,6 +14,8 @@ const (
|
|||
CompleteNoticeShow = "complete:notice:show"
|
||||
CompleteNoticeList = "complete:notice:list"
|
||||
|
||||
CompleteAliasResolve = "complete:alias:resolve"
|
||||
|
||||
CompleteInviteAccept = "complete:invite:accept"
|
||||
CompleteInviteConsume = "complete:invite:consume"
|
||||
)
|
||||
|
@ -28,6 +30,8 @@ func CompleteApp() *mux.Router {
|
|||
m.Path("/").Methods("GET").Name(CompleteIndex)
|
||||
m.Path("/about").Methods("GET").Name(CompleteAbout)
|
||||
|
||||
m.Path("/{alias}").Methods("GET").Name(CompleteAliasResolve)
|
||||
|
||||
m.Path("/invite/accept").Methods("GET").Name(CompleteInviteAccept)
|
||||
m.Path("/invite/consume").Methods("POST").Name(CompleteInviteConsume)
|
||||
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
{{ define "title" }}{{i18n "AdminAliasesRevokeConfirmTitle"}}{{ end }}
|
||||
{{ define "content" }}
|
||||
<div class="flex flex-col justify-center items-center h-64">
|
||||
|
||||
<span
|
||||
id="welcome"
|
||||
class="text-center"
|
||||
>{{i18n "AdminAliasesRevokeConfirmWelcome"}}</span>
|
||||
|
||||
<pre
|
||||
id="verify"
|
||||
class="my-4 font-mono truncate max-w-full text-lg text-gray-700"
|
||||
>{{.Entry.Feed.Ref}}</pre>
|
||||
|
||||
<form id="confirm" action="{{urlTo "admin:aliases:revoke"}}" method="POST">
|
||||
{{ .csrfField }}
|
||||
<input type="hidden" name="name" value={{.Entry.Name}}>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<a
|
||||
href="javascript:history.back()"
|
||||
class="px-4 h-8 shadow rounded flex flex-row justify-center items-center bg-white align-middle text-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-300 focus:ring-opacity-50"
|
||||
>{{i18n "GenericGoBack"}}</a>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="shadow rounded px-4 h-8 text-gray-100 bg-pink-600 hover:bg-pink-700 focus:outline-none focus:ring-2 focus:ring-pink-600 focus:ring-opacity-50"
|
||||
>{{i18n "GenericConfirm"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
|
@ -0,0 +1,72 @@
|
|||
{{ define "title" }}{{i18n "AdminAliasesTitle"}}{{ end }}
|
||||
{{ define "content" }}
|
||||
<h1
|
||||
class="text-3xl tracking-tight font-black text-black mt-2 mb-4"
|
||||
>{{i18n "AdminAliasesTitle"}}</h1>
|
||||
|
||||
<p id="welcome" class="my-2">{{i18n "AdminAliasesWelcome"}}</p>
|
||||
|
||||
<p
|
||||
id="aliasCount"
|
||||
class="text-lg font-bold my-2"
|
||||
>{{i18npl "ListCount" .Count}}</p>
|
||||
|
||||
<ul id="theList" class="divide-y pb-4">
|
||||
{{range .Entries}}
|
||||
<li class="flex flex-row items-center h-12">
|
||||
<span
|
||||
class="font-mono truncate flex-auto text-gray-600 tracking-wider"
|
||||
>{{.Name}}</span>
|
||||
<span
|
||||
class="font-mono truncate flex-auto text-gray-600 tracking-wider"
|
||||
>{{.Feed.Ref}}</span>
|
||||
|
||||
<a
|
||||
href="{{urlTo "admin:aliases:revoke:confirm" "id" .ID}}"
|
||||
class="pl-4 w-20 py-2 text-center text-gray-400 hover:text-red-600 font-bold cursor-pointer"
|
||||
>{{i18n "AdminAliasesRevoke"}}</a>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
|
||||
{{$pageNums := .Paginator.PageNums}}
|
||||
{{$view := .View}}
|
||||
{{if gt $pageNums 1}}
|
||||
<div class="flex flex-row justify-center">
|
||||
{{if not .FirstInView}}
|
||||
<a
|
||||
href="{{urlTo "admin:allow-list:overview"}}?page=1"
|
||||
class="rounded px-3 py-2 text-pink-600 border-transparent hover:border-pink-400 border-2"
|
||||
>1</a>
|
||||
<span
|
||||
class="px-3 py-2 text-gray-400 border-2 border-transparent"
|
||||
>..</span>
|
||||
{{end}}
|
||||
|
||||
{{range $view.Pages}}
|
||||
{{if le . $pageNums}}
|
||||
{{if eq . $view.Current}}
|
||||
<span
|
||||
class="px-3 py-2 cursor-default text-gray-500 border-2 border-transparent"
|
||||
>{{.}}</span>
|
||||
{{else}}
|
||||
<a
|
||||
href="{{urlTo "admin:allow-list:overview"}}?page={{.}}"
|
||||
class="rounded px-3 py-2 mx-1 text-pink-600 border-transparent hover:border-pink-400 border-2"
|
||||
>{{.}}</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{if not .LastInView}}
|
||||
<span
|
||||
class="px-3 py-2 text-gray-400 border-2 border-transparent"
|
||||
>..</span>
|
||||
<a
|
||||
href="{{urlTo "admin:allow-list:overview"}}?page={{$view.Last}}"
|
||||
class="rounded px-3 py-2 text-pink-600 border-transparent hover:border-pink-400 border-2"
|
||||
>{{$view.Last}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
|
@ -0,0 +1,9 @@
|
|||
{{ define "title" }}{{.Alias.Name}}{{ end }}
|
||||
{{ define "content" }}
|
||||
<div>
|
||||
<h1>{{.Alias.Name}}</h1>
|
||||
|
||||
<pre>{{.RoomAddr}}</pre>
|
||||
<p class="color-red-600">TODO: ssb-uri</p>
|
||||
</div>
|
||||
{{end}}
|
|
@ -18,6 +18,15 @@
|
|||
</svg>{{i18n "NavAdminDashboard"}}
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="{{urlTo "admin:aliases:overview"}}"
|
||||
class="{{if current_page_is "admin:aliases:overview"}}bg-gray-300 {{else}}hover:bg-gray-200 {{end}}pr-1 pl-2 py-3 sm:py-1 rounded-md flex flex-row items-center font-semibold text-sm text-gray-700 hover:text-gray-800 truncate"
|
||||
>
|
||||
<svg class="text-green-600 w-4 h-4 mr-1" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M23,12L20.56,9.22L20.9,5.54L17.29,4.72L15.4,1.54L12,3L8.6,1.54L6.71,4.72L3.1,5.53L3.44,9.21L1,12L3.44,14.78L3.1,18.47L6.71,19.29L8.6,22.47L12,21L15.4,22.46L17.29,19.28L20.9,18.46L20.56,14.78L23,12M10,17L6,13L7.41,11.59L10,14.17L16.59,7.58L18,9L10,17Z" />
|
||||
</svg>{{i18n "AdminAliasesTitle"}}
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="{{urlTo "admin:allow-list:overview"}}"
|
||||
class="{{if current_page_is "admin:allow-list:overview"}}bg-gray-300 {{else}}hover:bg-gray-200 {{end}}pr-1 pl-2 py-3 sm:py-1 rounded-md flex flex-row items-center font-semibold text-sm text-gray-700 hover:text-gray-800 truncate"
|
||||
|
|
Loading…
Reference in New Issue