Skip to content

Commit

Permalink
Merge pull request #59 from ssb-ngi-pointer/invite-pages
Browse files Browse the repository at this point in the history
Invite pages
  • Loading branch information
cryptix authored Mar 10, 2021
2 parents bbcab73 + b95c8de commit 04824ee
Show file tree
Hide file tree
Showing 29 changed files with 1,485 additions and 148 deletions.
10 changes: 8 additions & 2 deletions admindb/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,22 @@ type AliasService interface{}

// InviteService manages creation and consumption of invite tokens for joining the room.
type InviteService interface {

// Create creates a new invite for a new member. It returns the token or an error.
// createdBy is user ID of the admin or moderator who created it.
// aliasSuggestion is optional (empty string is fine) but can be used to disambiguate open invites. (See https://github.com/ssb-ngi-pointer/rooms2/issues/21)
Create(ctx context.Context, createdBy int64, aliasSuggestion string) (string, error)

// Consume checks if the passed token is still valid. If it is it adds newMember to the members of the room and invalidates the token.
// Consume checks if the passed token is still valid.
// If it is it adds newMember to the members of the room and invalidates the token.
// If the token isn't valid, it returns an error.
Consume(ctx context.Context, token string, newMember refs.FeedRef) (Invite, error)

// GetByToken returns the Invite if one for that token exists, or an error
GetByToken(ctx context.Context, token string) (Invite, error)

// GetByToken returns the Invite if one for that ID exists, or an error
GetByID(ctx context.Context, id int64) (Invite, error)

// List returns a list of all the valid invites
List(ctx context.Context) ([]Invite, error)

Expand Down
162 changes: 162 additions & 0 deletions admindb/mockdb/invite.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

83 changes: 71 additions & 12 deletions admindb/sqlite/invites.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ type Invites struct {
allowList *AllowList
}

const tokenLength = 50

// Create creates a new invite for a new member. It returns the token or an error.
// createdBy is user ID of the admin or moderator who created it.
// aliasSuggestion is optional (empty string is fine) but can be used to disambiguate open invites. (See https://github.com/ssb-ngi-pointer/rooms2/issues/21)
Expand Down Expand Up @@ -90,20 +88,11 @@ func (i Invites) Create(ctx context.Context, createdBy int64, aliasSuggestion st
func (i Invites) Consume(ctx context.Context, token string, newMember refs.FeedRef) (admindb.Invite, error) {
var inv admindb.Invite

tokenBytes, err := base64.URLEncoding.DecodeString(token)
hashedToken, err := getHashedToken(token)
if err != nil {
return inv, err
}

if n := len(tokenBytes); n != tokenLength {
return inv, fmt.Errorf("admindb: invalid invite token length (only got %d bytes)", n)
}

// hash the binary of the passed token
h := sha256.New()
h.Write(tokenBytes)
hashedToken := fmt.Sprintf("%x", h.Sum(nil))

err = transact(i.db, func(tx *sql.Tx) error {
entry, err := models.Invites(
qm.Where("active = true AND token = ?", hashedToken),
Expand Down Expand Up @@ -153,6 +142,55 @@ func deleteConsumedInvites(tx boil.ContextExecutor) error {
return nil
}

func (i Invites) GetByToken(ctx context.Context, token string) (admindb.Invite, error) {
var inv admindb.Invite

ht, err := getHashedToken(token)
if err != nil {
return inv, err
}

entry, err := models.Invites(
qm.Where("active = true AND token = ?", ht),
qm.Load("CreatedByAuthFallback"),
).One(ctx, i.db)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return inv, admindb.ErrNotFound
}
return inv, err
}

inv.ID = entry.ID
inv.AliasSuggestion = entry.AliasSuggestion
inv.CreatedBy.ID = entry.R.CreatedByAuthFallback.ID
inv.CreatedBy.Name = entry.R.CreatedByAuthFallback.Name

return inv, nil
}

func (i Invites) GetByID(ctx context.Context, id int64) (admindb.Invite, error) {
var inv admindb.Invite

entry, err := models.Invites(
qm.Where("active = true AND id = ?", id),
qm.Load("CreatedByAuthFallback"),
).One(ctx, i.db)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return inv, admindb.ErrNotFound
}
return inv, err
}

inv.ID = entry.ID
inv.AliasSuggestion = entry.AliasSuggestion
inv.CreatedBy.ID = entry.R.CreatedByAuthFallback.ID
inv.CreatedBy.Name = entry.R.CreatedByAuthFallback.Name

return inv, nil
}

// List returns a list of all the valid invites
func (i Invites) List(ctx context.Context) ([]admindb.Invite, error) {
var invs []admindb.Invite
Expand Down Expand Up @@ -193,6 +231,9 @@ func (i Invites) Revoke(ctx context.Context, id int64) error {
qm.Where("active = true AND id = ?", id),
).One(ctx, tx)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return admindb.ErrNotFound
}
return err
}

Expand All @@ -205,3 +246,21 @@ func (i Invites) Revoke(ctx context.Context, id int64) error {
return nil
})
}

const tokenLength = 50

func getHashedToken(b64tok string) (string, error) {
tokenBytes, err := base64.URLEncoding.DecodeString(b64tok)
if err != nil {
return "", err
}

if n := len(tokenBytes); n != tokenLength {
return "", fmt.Errorf("admindb: invalid invite token length (only got %d bytes)", n)
}

// hash the binary of the passed token
h := sha256.New()
h.Write(tokenBytes)
return fmt.Sprintf("%x", h.Sum(nil)), nil
}
8 changes: 8 additions & 0 deletions admindb/sqlite/invites_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,18 +68,26 @@ func TestInvites(t *testing.T) {
r.Equal("bestie", lst[0].AliasSuggestion)
r.Equal(testUserName, lst[0].CreatedBy.Name)

nope := db.AllowList.HasFeed(ctx, newMember)
r.False(nope, "expected feed to not yet be on the allow list")

inv, err := db.Invites.Consume(ctx, tok, newMember)
r.NoError(err, "failed to consume the invite")
r.Equal(testUserName, inv.CreatedBy.Name)
r.NotEqualValues(0, inv.ID, "invite ID unset")

// consume also adds it to the allow list
yes := db.AllowList.HasFeed(ctx, newMember)
r.True(yes, "expected feed on the allow list")

lst, err = db.Invites.List(ctx)
r.NoError(err, "failed to get list of tokens post consume")
r.Len(lst, 0, "expected no active invites")

// can't use twice
_, err = db.Invites.Consume(ctx, tok, newMember)
r.Error(err, "failed to consume the invite")

})

t.Run("simple create but revoke before use", func(t *testing.T) {
Expand Down
Loading

0 comments on commit 04824ee

Please sign in to comment.