|
@@ -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
|
|
|
}
|