3
0

Improve handle checking & generation

This commit is contained in:
Denis Arh
2020-02-27 14:19:28 +01:00
parent bb2597b712
commit 9405fb84c3
4 changed files with 91 additions and 10 deletions

View File

@@ -2,12 +2,39 @@ package handle
import (
"regexp"
"strings"
)
var (
c = regexp.MustCompile(`^[A-Za-z][0-9A-Za-z_\-.]*[A-Za-z0-9]$`)
validHandle = regexp.MustCompile(`^[A-Za-z][0-9A-Za-z_\-.]*[A-Za-z0-9]$`)
invalidChars = regexp.MustCompile(`[^0-9A-Za-z_\-.]+`)
)
func IsValid(s string) bool {
return s == "" || (len(s) >= 2 && c.MatchString(s))
return s == "" || (len(s) >= 2 && validHandle.MatchString(s))
}
// Cast transforms candidates to find a valid (non-empty) handle
func Cast(check func(string) bool, candidates ...string) (handle string, ok bool) {
ok = true
for _, c := range candidates {
if c == "" {
continue
}
// Capitalize
handle = strings.ReplaceAll(c[:1]+strings.Title(c)[1:], " ", "")
handle = invalidChars.ReplaceAllString(handle, "")
if handle == "" {
continue
}
if IsValid(handle) && (check == nil || check(handle)) {
return
}
}
return "", false
}

View File

@@ -14,6 +14,7 @@ import (
"golang.org/x/crypto/bcrypt"
"github.com/cortezaproject/corteza-server/pkg/eventbus"
"github.com/cortezaproject/corteza-server/pkg/handle"
"github.com/cortezaproject/corteza-server/pkg/logger"
"github.com/cortezaproject/corteza-server/pkg/permissions"
"github.com/cortezaproject/corteza-server/pkg/rand"
@@ -238,7 +239,10 @@ func (svc auth) External(profile goth.User) (u *types.User, err error) {
Email: profile.Email,
Name: profile.Name,
Username: profile.NickName,
Handle: profile.NickName,
}
if !handle.IsValid(profile.NickName) {
u.Handle = profile.NickName
}
if err = svc.CanRegister(); err != nil {
@@ -248,6 +252,9 @@ func (svc auth) External(profile goth.User) (u *types.User, err error) {
if err = svc.eventbus.WaitFor(svc.ctx, event.AuthBeforeSignup(u, authProvider)); err != nil {
return err
}
if u.Handle == "" {
createHandle(svc.users, u)
}
if u, err = svc.users.Create(u); err != nil {
return errors.Wrap(err, "could not create user after successful external authentication")
@@ -328,6 +335,10 @@ func (svc auth) InternalSignUp(input *types.User, password string) (u *types.Use
return
}
if !handle.IsValid(input.Handle) {
return nil, ErrInvalidHandle.withStack()
}
existing, err := svc.users.FindByEmail(input.Email)
if err == nil && existing.Valid() {
@@ -345,6 +356,12 @@ func (svc auth) InternalSignUp(input *types.User, password string) (u *types.Use
return nil, errors.Wrap(err, "user with this email already exists")
}
// We're not actually doing sign-up here - user exists,
// password is a match, so lets trigger before/after user login events
if err = svc.eventbus.WaitFor(svc.ctx, event.AuthBeforeLogin(existing, &types.AuthProvider{})); err != nil {
return nil, err
}
if !existing.EmailConfirmed {
err = svc.sendEmailAddressConfirmationToken(existing)
if err != nil {
@@ -352,12 +369,6 @@ func (svc auth) InternalSignUp(input *types.User, password string) (u *types.Use
}
}
// We're not actually doing sign-up here - user exists,
// password is a match, so lets trigger before/after user login events
if err = svc.eventbus.WaitFor(svc.ctx, event.AuthBeforeLogin(existing, &types.AuthProvider{})); err != nil {
return nil, err
}
defer svc.eventbus.Dispatch(svc.ctx, event.AuthAfterLogin(existing, &types.AuthProvider{}))
return existing, nil
@@ -395,6 +406,10 @@ func (svc auth) InternalSignUp(input *types.User, password string) (u *types.Use
return
}
if input.Handle == "" {
createHandle(svc.users, input)
}
// Whitelisted user data to copy
u, err = svc.users.Create(new)

View File

@@ -100,13 +100,18 @@ func TestAuth_External_NonExisting(t *testing.T) {
Return(c, nil)
usrRpoMock := repomock.NewMockUserRepository(mockCtrl)
usrRpoMock.EXPECT().
FindByHandle("foo").
Times(1).
Return(nil, repository.ErrUserNotFound)
usrRpoMock.EXPECT().
FindByEmail(u.Email).
Times(1).
Return(nil, repository.ErrUserNotFound)
usrRpoMock.EXPECT().
Create(&types.User{Email: "foo@example.tld"}).
Create(&types.User{Email: "foo@example.tld", Handle: "foo"}).
Times(1).
Return(u, nil)

View File

@@ -2,7 +2,9 @@ package service
import (
"context"
"github.com/cortezaproject/corteza-server/pkg/handle"
"io"
"regexp"
"strconv"
"strings"
@@ -242,6 +244,10 @@ func (svc user) Create(new *types.User) (u *types.User, err error) {
return nil, ErrNoCreatePermissions.withStack()
}
if !handle.IsValid(new.Handle) {
return nil, ErrInvalidHandle.withStack()
}
if svc.subscription != nil {
// When we have an active subscription, we need to check
// if users can be creare or did this deployment hit
@@ -256,6 +262,10 @@ func (svc user) Create(new *types.User) (u *types.User, err error) {
return
}
if new.Handle == "" {
createHandle(svc.user, new)
}
return u, svc.db.Transaction(func() (err error) {
if err = svc.UniqueCheck(new); err != nil {
@@ -281,6 +291,10 @@ func (svc user) Update(upd *types.User) (u *types.User, err error) {
return nil, ErrInvalidID.withStack()
}
if !handle.IsValid(upd.Handle) {
return nil, ErrInvalidHandle.withStack()
}
if u, err = svc.user.FindByID(upd.ID); err != nil {
return
}
@@ -473,3 +487,23 @@ func (svc user) handlePrivateData(u *types.User) {
u.Name = maskPrivateDataName
}
}
func createHandle(r repository.UserRepository, u *types.User) {
if u.Handle == "" {
u.Handle, _ = handle.Cast(
// Must not exist before
func(s string) bool {
e, err := r.FindByHandle(s)
return err == repository.ErrUserNotFound && (e == nil || e.ID == u.ID)
},
// use name or username
u.Name,
u.Username,
// use email w/o domain
regexp.
MustCompile("(@.*)$").
ReplaceAllString(u.Email, ""),
//
)
}
}