aboutsummaryrefslogtreecommitdiff
path: root/internal/db/users.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/db/users.go')
-rw-r--r--internal/db/users.go181
1 files changed, 169 insertions, 12 deletions
diff --git a/internal/db/users.go b/internal/db/users.go
index 006e2089..10935b4a 100644
--- a/internal/db/users.go
+++ b/internal/db/users.go
@@ -7,10 +7,12 @@ package db
import (
"fmt"
"strings"
+ "time"
"github.com/jinzhu/gorm"
"github.com/pkg/errors"
+ "gogs.io/gogs/internal/cryptoutil"
"gogs.io/gogs/internal/errutil"
)
@@ -30,15 +32,35 @@ type UsersStore interface {
// When the "loginSourceID" is positive, it tries to authenticate via given
// login source and creates a new user when not yet exists in the database.
Authenticate(username, password string, loginSourceID int64) (*User, error)
+ // Create creates a new user and persist to database.
+ // It returns ErrUserAlreadyExist when a user with same name already exists,
+ // or ErrEmailAlreadyUsed if the email has been used by another user.
+ Create(opts CreateUserOpts) (*User, error)
+ // GetByEmail returns the user (not organization) with given email.
+ // It ignores records with unverified emails and returns ErrUserNotExist when not found.
+ GetByEmail(email string) (*User, error)
// GetByID returns the user with given ID. It returns ErrUserNotExist when not found.
GetByID(id int64) (*User, error)
- // GetByUsername returns the user with given username. It returns ErrUserNotExist
- // when not found.
+ // GetByUsername returns the user with given username. It returns ErrUserNotExist when not found.
GetByUsername(username string) (*User, error)
}
var Users UsersStore
+// NOTE: This is a GORM create hook.
+func (u *User) BeforeCreate() {
+ u.CreatedUnix = gorm.NowFunc().Unix()
+ u.UpdatedUnix = u.CreatedUnix
+}
+
+// NOTE: This is a GORM query hook.
+func (u *User) AfterFind() {
+ u.Created = time.Unix(u.CreatedUnix, 0).Local()
+ u.Updated = time.Unix(u.UpdatedUnix, 0).Local()
+}
+
+var _ UsersStore = (*users)(nil)
+
type users struct {
*gorm.DB
}
@@ -51,14 +73,14 @@ func (err ErrLoginSourceMismatch) Error() string {
return fmt.Sprintf("login source mismatch: %v", err.args)
}
-func (db *users) Authenticate(username, password string, loginSourceID int64) (*User, error) {
- username = strings.ToLower(username)
+func (db *users) Authenticate(login, password string, loginSourceID int64) (*User, error) {
+ login = strings.ToLower(login)
var query *gorm.DB
- if strings.Contains(username, "@") {
- query = db.Where("email = ?", username)
+ if strings.Contains(login, "@") {
+ query = db.Where("email = ?", login)
} else {
- query = db.Where("lower_name = ?", username)
+ query = db.Where("lower_name = ?", login)
}
user := new(User)
@@ -89,7 +111,7 @@ func (db *users) Authenticate(username, password string, loginSourceID int64) (*
return nil, errors.Wrap(err, "get login source")
}
- _, err = authenticateViaLoginSource(source, username, password, false)
+ _, err = authenticateViaLoginSource(source, login, password, false)
if err != nil {
return nil, errors.Wrap(err, "authenticate via login source")
}
@@ -98,7 +120,7 @@ func (db *users) Authenticate(username, password string, loginSourceID int64) (*
// Non-local login source is always greater than 0.
if loginSourceID <= 0 {
- return nil, ErrUserNotExist{args: map[string]interface{}{"name": username}}
+ return nil, ErrUserNotExist{args: map[string]interface{}{"login": login}}
}
source, err := LoginSources.GetByID(loginSourceID)
@@ -106,19 +128,154 @@ func (db *users) Authenticate(username, password string, loginSourceID int64) (*
return nil, errors.Wrap(err, "get login source")
}
- user, err = authenticateViaLoginSource(source, username, password, true)
+ user, err = authenticateViaLoginSource(source, login, password, true)
if err != nil {
return nil, errors.Wrap(err, "authenticate via login source")
}
return user, nil
}
+type CreateUserOpts struct {
+ Name string
+ Email string
+ Password string
+ LoginSource int64
+ Activated bool
+}
+
+type ErrUserAlreadyExist struct {
+ args errutil.Args
+}
+
+func IsErrUserAlreadyExist(err error) bool {
+ _, ok := err.(ErrUserAlreadyExist)
+ return ok
+}
+
+func (err ErrUserAlreadyExist) Error() string {
+ return fmt.Sprintf("user already exists: %v", err.args)
+}
+
+type ErrEmailAlreadyUsed struct {
+ args errutil.Args
+}
+
+func IsErrEmailAlreadyUsed(err error) bool {
+ _, ok := err.(ErrEmailAlreadyUsed)
+ return ok
+}
+
+func (err ErrEmailAlreadyUsed) Email() string {
+ email, ok := err.args["email"].(string)
+ if ok {
+ return email
+ }
+ return "<email not found>"
+}
+
+func (err ErrEmailAlreadyUsed) Error() string {
+ return fmt.Sprintf("email has been used: %v", err.args)
+}
+
+func (db *users) Create(opts CreateUserOpts) (*User, error) {
+ err := isUsernameAllowed(opts.Name)
+ if err != nil {
+ return nil, err
+ }
+
+ _, err = db.GetByUsername(opts.Name)
+ if err == nil {
+ return nil, ErrUserAlreadyExist{args: errutil.Args{"name": opts.Name}}
+ } else if !IsErrUserNotExist(err) {
+ return nil, err
+ }
+
+ _, err = db.GetByEmail(opts.Email)
+ if err == nil {
+ return nil, ErrEmailAlreadyUsed{args: errutil.Args{"email": opts.Email}}
+ } else if !IsErrUserNotExist(err) {
+ return nil, err
+ }
+
+ user := &User{
+ LowerName: strings.ToLower(opts.Name),
+ Name: opts.Name,
+ Email: opts.Email,
+ Passwd: opts.Password,
+ LoginSource: opts.LoginSource,
+ MaxRepoCreation: -1,
+ IsActive: opts.Activated,
+ Avatar: cryptoutil.MD5(opts.Email),
+ AvatarEmail: opts.Email,
+ }
+
+ user.Rands, err = GetUserSalt()
+ if err != nil {
+ return nil, err
+ }
+ user.Salt, err = GetUserSalt()
+ if err != nil {
+ return nil, err
+ }
+ user.EncodePassword()
+
+ return user, db.DB.Create(user).Error
+}
+
+var _ errutil.NotFound = (*ErrUserNotExist)(nil)
+
+type ErrUserNotExist struct {
+ args errutil.Args
+}
+
+func IsErrUserNotExist(err error) bool {
+ _, ok := err.(ErrUserNotExist)
+ return ok
+}
+
+func (err ErrUserNotExist) Error() string {
+ return fmt.Sprintf("user does not exist: %v", err.args)
+}
+
+func (ErrUserNotExist) NotFound() bool {
+ return true
+}
+
+func (db *users) GetByEmail(email string) (*User, error) {
+ email = strings.ToLower(email)
+
+ if len(email) == 0 {
+ return nil, ErrUserNotExist{args: errutil.Args{"email": email}}
+ }
+
+ // First try to find the user by primary email
+ user := new(User)
+ err := db.Where("email = ? AND type = ? AND is_active = ?", email, UserIndividual, true).First(user).Error
+ if err == nil {
+ return user, nil
+ } else if !gorm.IsRecordNotFoundError(err) {
+ return nil, err
+ }
+
+ // Otherwise, check activated email addresses
+ emailAddress := new(EmailAddress)
+ err = db.Where("email = ? AND is_activated = ?", email, true).First(emailAddress).Error
+ if err != nil {
+ if gorm.IsRecordNotFoundError(err) {
+ return nil, ErrUserNotExist{args: errutil.Args{"email": email}}
+ }
+ return nil, err
+ }
+
+ return db.GetByID(emailAddress.UID)
+}
+
func (db *users) GetByID(id int64) (*User, error) {
user := new(User)
err := db.Where("id = ?", id).First(user).Error
if err != nil {
if gorm.IsRecordNotFoundError(err) {
- return nil, ErrUserNotExist{args: map[string]interface{}{"userID": id}}
+ return nil, ErrUserNotExist{args: errutil.Args{"userID": id}}
}
return nil, err
}
@@ -130,7 +287,7 @@ func (db *users) GetByUsername(username string) (*User, error) {
err := db.Where("lower_name = ?", strings.ToLower(username)).First(user).Error
if err != nil {
if gorm.IsRecordNotFoundError(err) {
- return nil, ErrUserNotExist{args: map[string]interface{}{"name": username}}
+ return nil, ErrUserNotExist{args: errutil.Args{"name": username}}
}
return nil, err
}