Improve handle checking & generation
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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, ""),
|
||||
//
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user