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