diff --git a/main.go b/main.go index 7ba510b..3483d0b 100644 --- a/main.go +++ b/main.go @@ -41,14 +41,14 @@ func main() { // Init Config err := config.InitConfig() if err != nil { - log.Log.Error(err.Error()) + log.Log.Fatal(err.Error()) os.Exit(1) } // Set Engine err = models.SetEngine() if err != nil { - log.Log.Error(err.Error()) + log.Log.Fatal(err.Error()) os.Exit(1) } @@ -58,6 +58,16 @@ func main() { // Additional swagger information docs.SwaggerInfo.Version = Version + // Create first admin if needed + firstAdmin, err := models.CreateFirstAdmin() + if err != nil { + log.Log.Fatal("Could not create first admin.", err) + os.Exit(1) + } + if firstAdmin != nil { + log.Log.Infof("Created first admin user with name %s", firstAdmin.Username) + } + // Start the webserver e := routes.NewEcho() routes.RegisterRoutes(e) diff --git a/pkg/config/config.go b/pkg/config/config.go index 6542097..414ac65 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -55,6 +55,9 @@ func InitConfig() (err error) { viper.SetDefault("database.path", "./sofaraum.db") viper.SetDefault("database.showqueries", false) viper.SetDefault("database.openconnections", 100) + // First Admin + viper.SetDefault("firstadmin.username", "admin") + viper.SetDefault("firstadmin.password", "admin") // Cacher viper.SetDefault("cache.enabled", false) viper.SetDefault("cache.type", "memory") diff --git a/pkg/models/error.go b/pkg/models/error.go index 0f4080b..a9ac716 100644 --- a/pkg/models/error.go +++ b/pkg/models/error.go @@ -150,3 +150,51 @@ const ErrCodeCouldNotGetUserID = 2003 func (err ErrCouldNotGetUserID) HTTPError() web.HTTPError { return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeCouldNotGetUserID, Message: "Could not get user id."} } + +// ErrUsernameExists represents a "UsernameAlreadyExists" kind of error. +type ErrUsernameExists struct { + UserID int64 + Username string +} + +// IsErrUsernameExists checks if an error is a ErrUsernameExists. +func IsErrUsernameExists(err error) bool { + _, ok := err.(ErrUsernameExists) + return ok +} + +func (err ErrUsernameExists) Error() string { + return fmt.Sprintf("User with that username already exists [user id: %d, username: %s]", err.UserID, err.Username) +} + +// ErrorCodeUsernameExists holds the unique world-error code of this error +const ErrorCodeUsernameExists = 2004 + +// HTTPError holds the http error description +func (err ErrUsernameExists) HTTPError() web.HTTPError { + return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrorCodeUsernameExists, Message: "A user with this username already exists."} +} + +// ErrUserEmailExists represents a "UserEmailExists" kind of error. +type ErrUserEmailExists struct { + UserID int64 + Email string +} + +// IsErrUserEmailExists checks if an error is a ErrUserEmailExists. +func IsErrUserEmailExists(err error) bool { + _, ok := err.(ErrUserEmailExists) + return ok +} + +func (err ErrUserEmailExists) Error() string { + return fmt.Sprintf("User with that email already exists [user id: %d, email: %s]", err.UserID, err.Email) +} + +// ErrorCodeUserEmailExists holds the unique world-error code of this error +const ErrorCodeUserEmailExists = 2005 + +// HTTPError holds the http error description +func (err ErrUserEmailExists) HTTPError() web.HTTPError { + return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrorCodeUserEmailExists, Message: "A user with this email address already exists."} +} diff --git a/pkg/models/user.go b/pkg/models/user.go index 0b10814..a6f2f0e 100644 --- a/pkg/models/user.go +++ b/pkg/models/user.go @@ -23,6 +23,7 @@ import ( "fmt" "github.com/dgrijalva/jwt-go" "github.com/labstack/echo" + "github.com/spf13/viper" "golang.org/x/crypto/bcrypt" "reflect" ) @@ -83,8 +84,8 @@ type APIUserPassword struct { } // APIFormat formats an API User into a normal user struct -func (apiUser *APIUserPassword) APIFormat() User { - return User{ +func (apiUser *APIUserPassword) APIFormat() *User { + return &User{ ID: apiUser.ID, Username: apiUser.Username, Password: apiUser.Password, @@ -155,3 +156,21 @@ func GetCurrentUser(c echo.Context) (user *User, err error) { return } + +// CreateFirstAdmin checks if there is at least one user and creates a new one if not +func CreateFirstAdmin() (firstAdmin *User, err error) { + count, err := x.Count(User{}) + if err != nil { + return + } + + if count < 1 { + firstAdmin, err = CreateUser(&User{ + Username: viper.GetString("firstadmin.username"), + Password: viper.GetString("firstadmin.password"), + }) + return + } + + return +} diff --git a/pkg/models/user_create_update.go b/pkg/models/user_create_update.go new file mode 100644 index 0000000..aafd012 --- /dev/null +++ b/pkg/models/user_create_update.go @@ -0,0 +1,141 @@ +// Sofaraum server is the server which collects the statistics +// for the sofaraum-heatmap application. +// Copyright 2018 K.Langenberg and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package models + +import ( + "golang.org/x/crypto/bcrypt" +) + +// CreateUser creates a new user and inserts it into the database +func CreateUser(user *User) (newUser *User, err error) { + + newUser = user + + // Check if we have all needed informations + if newUser.Password == "" || newUser.Username == "" { + return &User{}, ErrNoUsernamePassword{} + } + + // Check if the user already existst with that username + var exists = true + existingUser, err := GetUser(&User{Username: newUser.Username}) + if err != nil { + if IsErrUserDoesNotExist(err) { + exists = false + } else { + return &User{}, err + } + } + if exists { + return &User{}, ErrUsernameExists{newUser.ID, newUser.Username} + } + + // Check if the user already existst with that email + if newUser.Email != "" { + existingUser, err = GetUser(&User{Email: newUser.Email}) + if err != nil { + if IsErrUserDoesNotExist(err) { + exists = false + } else { + return &User{}, err + } + } + if exists { + return &User{}, ErrUserEmailExists{existingUser.ID, existingUser.Email} + } + } + + // Hash the password + newUser.Password, err = hashPassword(user.Password) + if err != nil { + return &User{}, err + } + + newUser.IsActive = true + + // Insert it + _, err = x.Insert(newUser) + if err != nil { + return &User{}, err + } + + return newUser, err +} + +// HashPassword hashes a password +func hashPassword(password string) (string, error) { + bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14) + return string(bytes), err +} + +// UpdateUser updates a user +func UpdateUser(user *User) (updatedUser *User, err error) { + + // Check if it exists + theUser, err := GetUserByID(user.ID) + if err != nil { + return &User{}, err + } + + // Check if we have at least a username + if user.Username == "" { + //return User{}, ErrNoUsername{user.ID} + user.Username = theUser.Username // Dont change the username if we dont have one + } + + user.Password = theUser.Password // set the password to the one in the database to not accedently resetting it + + // Update it + _, err = x.Id(user.ID).Update(user) + if err != nil { + return &User{}, err + } + + // Get the newly updated user + updatedUser, err = GetUserByID(user.ID) + if err != nil { + return &User{}, err + } + + return updatedUser, err +} + +// UpdateUserPassword updates the password of a user +func UpdateUserPassword(user *User, newPassword string) (err error) { + + // Get all user details + theUser, err := GetUserByID(user.ID) + if err != nil { + return err + } + + // Hash the new password and set it + hashed, err := hashPassword(newPassword) + if err != nil { + return err + } + theUser.Password = hashed + + // Update it + _, err = x.Id(user.ID).Update(theUser) + if err != nil { + return err + } + + return err +} diff --git a/pkg/routes/api/v1/user_create.go b/pkg/routes/api/v1/user_create.go new file mode 100644 index 0000000..b9276e2 --- /dev/null +++ b/pkg/routes/api/v1/user_create.go @@ -0,0 +1,52 @@ +// Sofaraum server is the server which collects the statistics +// for the sofaraum-heatmap application. +// Copyright 2018 K.Langenberg and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package v1 + +import ( + "code.sofaraum.de/server/pkg/models" + "code.vikunja.io/web/handler" + "github.com/labstack/echo" + "net/http" +) + +// RegisterUser is the register handler +// @Summary Register +// @Description Creates a new user account. +// @tags user +// @Accept json +// @Produce json +// @Param credentials body models.APIUserPassword true "The user credentials" +// @Success 200 {object} models.User +// @Failure 400 {object} code.vikunja.io/web.HTTPError "No or invalid user register object provided / User already exists." +// @Failure 500 {object} models.Message "Internal error" +// @Router /register [post] +func CreateUser(c echo.Context) error { + // Check for Request Content + var datUser *models.APIUserPassword + if err := c.Bind(&datUser); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "No or invalid user model provided.") + } + + // Insert the user + newUser, err := models.CreateUser(datUser.APIFormat()) + if err != nil { + return handler.HandleHTTPError(err, c) + } + + return c.JSON(http.StatusOK, newUser) +} diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index 3527a60..12e8b60 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -117,4 +117,5 @@ func RegisterRoutes(e *echo.Echo) { // User stuff a.GET("/user", apiv1.UserShow) + a.POST("/user/new", apiv1.CreateUser) }