diff --git a/core/controllers/user.go b/core/controllers/user.go index 01def9c1..a7ae4143 100644 --- a/core/controllers/user.go +++ b/core/controllers/user.go @@ -27,14 +27,16 @@ func GetUserById(c *gin.Context) { } // get role - if !user.RoleId.IsZero() { - role, err := service.NewModelService[models.Role]().GetById(user.RoleId) - if err != nil { - HandleErrorInternalServerError(c, err) - return + if utils.IsPro() { + if !user.RoleId.IsZero() { + role, err := service.NewModelService[models.Role]().GetById(user.RoleId) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + user.Role = role.Name + user.IsAdmin = role.Admin } - user.Role = role.Name - user.IsAdmin = role.Admin } HandleSuccessWithData(c, user) @@ -62,31 +64,33 @@ func GetUserList(c *gin.Context) { } // get roles - var roleIds []primitive.ObjectID - for _, user := range users { - if !user.RoleId.IsZero() { - roleIds = append(roleIds, user.RoleId) - } - } - if len(roleIds) > 0 { - roles, err := service.NewModelService[models.Role]().GetMany(bson.M{ - "_id": bson.M{"$in": roleIds}, - }, nil) - if err != nil { - HandleErrorInternalServerError(c, err) - return - } - rolesMap := make(map[primitive.ObjectID]models.Role) - for _, role := range roles { - rolesMap[role.Id] = role - } - for i, user := range users { - if user.RoleId.IsZero() { - continue + if utils.IsPro() { + var roleIds []primitive.ObjectID + for _, user := range users { + if !user.RoleId.IsZero() { + roleIds = append(roleIds, user.RoleId) } - if role, ok := rolesMap[user.RoleId]; ok { - users[i].Role = role.Name - users[i].IsAdmin = role.Admin + } + if len(roleIds) > 0 { + roles, err := service.NewModelService[models.Role]().GetMany(bson.M{ + "_id": bson.M{"$in": roleIds}, + }, nil) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + rolesMap := make(map[primitive.ObjectID]models.Role) + for _, role := range roles { + rolesMap[role.Id] = role + } + for i, user := range users { + if user.RoleId.IsZero() { + continue + } + if role, ok := rolesMap[user.RoleId]; ok { + users[i].Role = role.Name + users[i].IsAdmin = role.Admin + } } } } @@ -144,7 +148,6 @@ func PostUser(c *gin.Context) { } HandleSuccessWithData(c, result) - } func PostUserChangePassword(c *gin.Context) { @@ -163,20 +166,24 @@ func PostUserChangePassword(c *gin.Context) { HandleErrorBadRequest(c, err) return } + if len(payload.Password) < 5 { + HandleErrorBadRequest(c, errors.New("password must be at least 5 characters")) + return + } // get user u := GetUserFromContext(c) modelSvc := service.NewModelService[models.User]() // update password - user, err := modelSvc.GetById(id) + userDb, err := modelSvc.GetById(id) if err != nil { HandleErrorInternalServerError(c, err) return } - user.SetUpdated(u.Id) - user.Password = utils.EncryptMd5(payload.Password) - if err := modelSvc.ReplaceById(user.Id, *user); err != nil { + userDb.SetUpdated(u.Id) + userDb.Password = utils.EncryptMd5(payload.Password) + if err := modelSvc.ReplaceById(userDb.Id, *userDb); err != nil { HandleErrorInternalServerError(c, err) return } @@ -192,6 +199,20 @@ func GetUserMe(c *gin.Context) { HandleErrorInternalServerError(c, err) return } + + if utils.IsPro() { + if !_u.RoleId.IsZero() { + r, err := service.NewModelService[models.Role]().GetById(_u.RoleId) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + _u.Role = r.Name + _u.IsAdmin = r.Admin + _u.Routes = r.Routes + } + } + HandleSuccessWithData(c, _u) } @@ -206,6 +227,7 @@ func PutUserById(c *gin.Context) { // get user u := GetUserFromContext(c) + // model service modelSvc := service.NewModelService[models.User]() // update user @@ -214,7 +236,17 @@ func PutUserById(c *gin.Context) { HandleErrorInternalServerError(c, err) return } + + // if root admin, disallow changing username and role + if userDb.RootAdmin { + user.Username = userDb.Username + user.RoleId = userDb.RoleId + } + + // disallow changing password user.Password = userDb.Password + + // update user user.SetUpdated(u.Id) if user.Id.IsZero() { user.Id = u.Id diff --git a/core/models/common/init_index.go b/core/models/common/init_index.go index 46390f98..b7aebc3b 100644 --- a/core/models/common/init_index.go +++ b/core/models/common/init_index.go @@ -62,8 +62,9 @@ func InitIndexes() { // users RecreateIndexes(mongo.GetMongoCol(service.GetCollectionNameByInstance(models.User{})), []mongo2.IndexModel{ - {Keys: bson.M{"username": 1}}, + {Keys: bson.M{"username": 1}, Options: (&options.IndexOptions{}).SetUnique(true)}, {Keys: bson.M{"role": 1}}, + {Keys: bson.M{"role_id": 1}}, {Keys: bson.M{"email": 1}}, }) @@ -76,9 +77,4 @@ func InitIndexes() { RecreateIndexes(mongo.GetMongoCol(service.GetCollectionNameByInstance(models.Token{})), []mongo2.IndexModel{ {Keys: bson.M{"name": 1}}, }) - - // data collections - RecreateIndexes(mongo.GetMongoCol(service.GetCollectionNameByInstance(models.DataCollection{})), []mongo2.IndexModel{ - {Keys: bson.M{"name": 1}}, - }) } diff --git a/core/models/models/user.go b/core/models/models/user.go index a794e357..356562c4 100644 --- a/core/models/models/user.go +++ b/core/models/models/user.go @@ -9,6 +9,10 @@ type User struct { Password string `json:"-" bson:"password"` Role string `json:"role" bson:"role"` RoleId primitive.ObjectID `json:"role_id" bson:"role_id"` + FirstName string `json:"first_name" bson:"first_name"` + LastName string `json:"last_name" bson:"last_name"` Email string `json:"email" bson:"email"` - IsAdmin bool `json:"admin" bson:"-"` + RootAdmin bool `json:"root_admin,omitempty" bson:"root_admin"` + IsAdmin bool `json:"admin,omitempty" bson:"-"` + Routes []string `json:"routes,omitempty" bson:"-"` } diff --git a/core/user/service.go b/core/user/service.go index 97133b1b..db32dad4 100644 --- a/core/user/service.go +++ b/core/user/service.go @@ -1,13 +1,13 @@ package user import ( + errors2 "errors" "github.com/apex/log" "github.com/crawlab-team/crawlab/core/constants" "github.com/crawlab-team/crawlab/core/errors" "github.com/crawlab-team/crawlab/core/models/models" "github.com/crawlab-team/crawlab/core/models/service" "github.com/crawlab-team/crawlab/core/utils" - mongo2 "github.com/crawlab-team/crawlab/db/mongo" "github.com/crawlab-team/crawlab/trace" "github.com/golang-jwt/jwt/v5" "go.mongodb.org/mongo-driver/bson" @@ -20,41 +20,74 @@ import ( type Service struct { jwtSecret string jwtSigningMethod jwt.SigningMethod - modelSvc *service.ModelService[models.User] } func (svc *Service) Init() (err error) { - _, err = svc.modelSvc.GetOne(bson.M{"username": constants.DefaultAdminUsername}, nil) - if err == nil { - return nil + if utils.IsPro() { + return svc.initPro() } - if err.Error() != mongo.ErrNoDocuments.Error() { - return err - } - return svc.Create( - constants.DefaultAdminUsername, - constants.DefaultAdminPassword, - constants.RoleAdmin, - "", - primitive.NilObjectID, - ) + return svc.init() } -func (svc *Service) SetJwtSecret(secret string) { - svc.jwtSecret = secret +func (svc *Service) init() (err error) { + _, err = service.NewModelService[models.User]().GetOne(bson.M{"username": constants.DefaultAdminUsername}, nil) + if err != nil { + if !errors2.Is(err, mongo.ErrNoDocuments) { + return err + } + } else { + // exists + return + } + + // add user + u := models.User{ + Username: constants.DefaultAdminUsername, + Password: utils.EncryptMd5(constants.DefaultAdminPassword), + Role: constants.RoleAdmin, + RootAdmin: true, + } + u.SetCreatedAt(time.Now()) + u.SetUpdatedAt(time.Now()) + _, err = service.NewModelService[models.User]().InsertOne(u) + return err } -func (svc *Service) SetJwtSigningMethod(method jwt.SigningMethod) { - svc.jwtSigningMethod = method +func (svc *Service) initPro() (err error) { + _, err = service.NewModelService[models.User]().GetOne(bson.M{ + "$or": []bson.M{ + {"username": constants.DefaultAdminUsername}, + {"root_admin": true}, + }, + }, nil) + if err != nil { + if !errors2.Is(err, mongo.ErrNoDocuments) { + return err + } + } else { + // exists + return + } + + // add user + u := models.User{ + Username: constants.DefaultAdminUsername, + Password: utils.EncryptMd5(constants.DefaultAdminPassword), + RootAdmin: true, + } + u.SetCreatedAt(time.Now()) + u.SetUpdatedAt(time.Now()) + _, err = service.NewModelService[models.User]().InsertOne(u) + return err } func (svc *Service) Create(username, password, role, email string, by primitive.ObjectID) (err error) { // validate options if username == "" || password == "" { - return trace.TraceError(errors.ErrorUserMissingRequiredFields) + return errors.ErrorUserMissingRequiredFields } if len(password) < 5 { - return trace.TraceError(errors.ErrorUserInvalidPassword) + return errors.ErrorUserInvalidPassword } // normalize options @@ -63,29 +96,48 @@ func (svc *Service) Create(username, password, role, email string, by primitive. } // check if user exists - if u, err := svc.modelSvc.GetOne(bson.M{"username": username}, nil); err == nil && u != nil && !u.Id.IsZero() { - return trace.TraceError(errors.ErrorUserAlreadyExists) + if u, err := service.NewModelService[models.User]().GetOne(bson.M{"username": username}, nil); err == nil && u != nil && !u.Id.IsZero() { + return errors.ErrorUserAlreadyExists } - // transaction - return mongo2.RunTransaction(func(ctx mongo.SessionContext) error { - // add user - u := models.User{ - Username: username, - Role: role, - Password: utils.EncryptMd5(password), - Email: email, - } - u.SetCreated(by) - u.SetUpdated(by) - _, err = svc.modelSvc.InsertOne(u) + // add user + u := models.User{ + Username: username, + Role: role, + Password: utils.EncryptMd5(password), + Email: email, + } + u.SetCreated(by) + u.SetUpdated(by) + _, err = service.NewModelService[models.User]().InsertOne(u) - return err - }) + return err +} + +func (svc *Service) CreateUser(u *models.User, by primitive.ObjectID) (err error) { + // validate options + if u.Username == "" || u.Password == "" { + return errors.ErrorUserMissingRequiredFields + } + if len(u.Password) < 5 { + return errors.ErrorUserInvalidPassword + } + + // check if user exists + if u, err := service.NewModelService[models.User]().GetOne(bson.M{"username": u.Username}, nil); err == nil && u != nil && !u.Id.IsZero() { + return errors.ErrorUserAlreadyExists + } + + // add user + u.SetCreated(by) + u.SetUpdated(by) + _, err = service.NewModelService[models.User]().InsertOne(*u) + + return err } func (svc *Service) Login(username, password string) (token string, u *models.User, err error) { - u, err = svc.modelSvc.GetOne(bson.M{"username": username}, nil) + u, err = service.NewModelService[models.User]().GetOne(bson.M{"username": username}, nil) if err != nil { return "", nil, err } @@ -104,13 +156,13 @@ func (svc *Service) CheckToken(tokenStr string) (u *models.User, err error) { } func (svc *Service) ChangePassword(id primitive.ObjectID, password string, by primitive.ObjectID) (err error) { - u, err := svc.modelSvc.GetById(id) + u, err := service.NewModelService[models.User]().GetById(id) if err != nil { return err } u.Password = utils.EncryptMd5(password) u.SetCreatedBy(by) - return svc.modelSvc.ReplaceById(id, *u) + return service.NewModelService[models.User]().ReplaceById(id, *u) } func (svc *Service) MakeToken(user *models.User) (tokenStr string, err error) { @@ -148,7 +200,7 @@ func (svc *Service) checkToken(tokenStr string) (user *models.User, err error) { return user, err } username := claim["username"].(string) - user, err = svc.modelSvc.GetById(id) + user, err = service.NewModelService[models.User]().GetById(id) if err != nil { err = errors.ErrorUserNotExists return @@ -171,7 +223,6 @@ func (svc *Service) getSecretFunc() jwt.Keyfunc { func newUserService() (svc *Service, err error) { // service svc = &Service{ - modelSvc: service.NewModelService[models.User](), jwtSecret: "crawlab", jwtSigningMethod: jwt.SigningMethodHS256, }