8000 Invite pages by cryptix · Pull Request #59 · ssbc/go-ssb-room · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Invite pages #59

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Mar 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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