Add create user rest endpoint
diff --git a/core/kg/api/rest/user_service.go b/core/kg/api/rest/user_service.go
index c129b78..42cf40c 100644
--- a/core/kg/api/rest/user_service.go
+++ b/core/kg/api/rest/user_service.go
@@ -1,8 +1,10 @@
package rest
import (
+ "encoding/json"
"net/http"
+ "github.com/giolekva/pcloud/core/kg/model"
"github.com/gorilla/mux"
"github.com/pkg/errors"
)
@@ -16,6 +18,18 @@
func (router *Router) buildCreateUserHandler() http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) error {
router.Logger.Debug("Rest API: create user")
+ var user *model.User
+ if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
+ return errors.Wrap(err, "can't decode request body")
+ }
+ user.SanitizeInput()
+ updatedUser, err := router.App.CreateUser(user)
+ if err != nil {
+ return errors.Wrap(err, "can't create user")
+ }
+ updatedUser.SanitizeOutput()
+
+ jsoner(w, http.StatusOK, updatedUser)
return nil
}
return HandlerFunc(fn)
diff --git a/core/kg/app/app.go b/core/kg/app/app.go
index 0c65fc8..0d8b312 100644
--- a/core/kg/app/app.go
+++ b/core/kg/app/app.go
@@ -1,19 +1,22 @@
package app
import (
+ "github.com/giolekva/pcloud/core/kg/model"
"github.com/giolekva/pcloud/core/kg/store"
)
// App represents an application layer of the kg
type App struct {
store store.Store
+ config *model.Config
logger logger
}
// NewApp creates new app
-func NewApp(store store.Store, logger logger) *App {
+func NewApp(store store.Store, config *model.Config, logger logger) *App {
return &App{
store: store,
+ config: config,
logger: logger,
}
}
diff --git a/core/kg/app/user.go b/core/kg/app/user.go
index 07ff301..dd25e84 100644
--- a/core/kg/app/user.go
+++ b/core/kg/app/user.go
@@ -1,6 +1,7 @@
package app
import (
+ "github.com/giolekva/pcloud/core/kg/log"
"github.com/giolekva/pcloud/core/kg/model"
"github.com/pkg/errors"
)
@@ -13,3 +14,24 @@
}
return user, nil
}
+
+func (a *App) CreateUser(user *model.User) (*model.User, error) {
+ if !a.isFirstUserAccount() {
+ return nil, errors.New("not a first user")
+ }
+
+ user.HashPassword()
+ updatedUser, err := a.store.User().Save(user)
+ if err != nil {
+ return nil, errors.Wrap(err, "can't save user to the DB")
+ }
+ return updatedUser, nil
+}
+
+func (a *App) isFirstUserAccount() bool {
+ count, err := a.store.User().Count()
+ if err != nil {
+ a.logger.Error("error fetching first user account", log.Err(err))
+ }
+ return count > 0
+}
diff --git a/core/kg/cmd/commands/root.go b/core/kg/cmd/commands/root.go
index 6316ab1..bb4136a 100644
--- a/core/kg/cmd/commands/root.go
+++ b/core/kg/cmd/commands/root.go
@@ -38,7 +38,7 @@
config := model.NewConfig()
st := memory.New()
- a := app.NewApp(st, logger)
+ a := app.NewApp(st, config, logger)
grpcServer := server.NewGRPCServer(logger, config, a)
httpServer := server.NewHTTPServer(logger, config, a)
diff --git a/core/kg/common/interfaces.go b/core/kg/common/interfaces.go
index daee20e..2c20e28 100644
--- a/core/kg/common/interfaces.go
+++ b/core/kg/common/interfaces.go
@@ -14,4 +14,5 @@
type AppIface interface {
GetUser(userID string) (*model.User, error)
+ CreateUser(user *model.User) (*model.User, error)
}
diff --git a/core/kg/go.mod b/core/kg/go.mod
index f6bf84a..05af918 100644
--- a/core/kg/go.mod
+++ b/core/kg/go.mod
@@ -11,7 +11,7 @@
github.com/spf13/cobra v1.1.3
github.com/stretchr/testify v1.7.0
go.uber.org/zap v1.16.0
- golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 // indirect
+ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
golang.org/x/tools v0.0.0-20200313205530-4303120df7d8 // indirect
google.golang.org/grpc v1.35.0
google.golang.org/protobuf v1.25.0
diff --git a/core/kg/go.sum b/core/kg/go.sum
index 37a24ce..1d580f2 100644
--- a/core/kg/go.sum
+++ b/core/kg/go.sum
@@ -229,6 +229,8 @@
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
+golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -264,8 +266,9 @@
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -288,12 +291,14 @@
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU=
-golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/core/kg/model/config.go b/core/kg/model/config.go
index fd8c034..cc556ff 100644
--- a/core/kg/model/config.go
+++ b/core/kg/model/config.go
@@ -17,6 +17,7 @@
SQL SQLConfig
HTTP HTTPConfig
GRPC GRPCConfig
+ App AppConfig
}
func NewConfig() *Config {
@@ -80,3 +81,15 @@
s.Port = defaultGRPCPort
}
}
+
+type AppConfig struct {
+ EnableSignUp *bool
+}
+
+func (s *AppConfig) SetDefaults() {
+ if s.EnableSignUp == nil {
+ s.EnableSignUp = NewBool(true)
+ }
+}
+
+func NewBool(b bool) *bool { return &b }
diff --git a/core/kg/model/user.go b/core/kg/model/user.go
index 156f4fa..5bcb8a0 100644
--- a/core/kg/model/user.go
+++ b/core/kg/model/user.go
@@ -5,6 +5,7 @@
"unicode"
"github.com/pkg/errors"
+ "golang.org/x/crypto/bcrypt"
)
const (
@@ -46,11 +47,40 @@
return nil
}
+// Clone clones the object
func (u *User) Clone() *User {
user := *u
return &user
}
+// SanitizeInput removes input data from the user object that is not user controlled
+func (u *User) SanitizeInput() {
+ u.ID = ""
+ u.CreateAt = 0
+ u.UpdateAt = 0
+ u.DeleteAt = 0
+ u.LastPasswordUpdate = 0
+}
+
+// SanitizeOutput removes output data from the user object that is not user controlled
+func (u *User) SanitizeOutput() {
+ u.Password = ""
+}
+
+// HashPassword hashes user's password
+func (u *User) HashPassword() {
+ if u.Password == "" {
+ return
+ }
+
+ hash, err := bcrypt.GenerateFromPassword([]byte(u.Password), 10)
+ if err != nil {
+ panic(err)
+ }
+
+ u.Password = string(hash)
+}
+
func isValidID(value string) bool {
if len(value) != 26 {
return false
diff --git a/core/kg/store/memory/user_store.go b/core/kg/store/memory/user_store.go
index 1805845..62d1ea6 100644
--- a/core/kg/store/memory/user_store.go
+++ b/core/kg/store/memory/user_store.go
@@ -63,3 +63,7 @@
}
return users, nil
}
+
+func (us *memoryUserStore) Count() (int64, error) {
+ return int64(us.maxID) - 1, nil
+}
diff --git a/core/kg/store/sqlstore/user_store.go b/core/kg/store/sqlstore/user_store.go
index f8a2d58..38a196a 100644
--- a/core/kg/store/sqlstore/user_store.go
+++ b/core/kg/store/sqlstore/user_store.go
@@ -49,3 +49,7 @@
func (us SqlUserStore) GetAll() ([]*model.User, error) {
return nil, nil
}
+
+func (us SqlUserStore) Count() (int64, error) {
+ return 0, nil
+}
diff --git a/core/kg/store/store.go b/core/kg/store/store.go
index d291e4a..3d4f4f7 100644
--- a/core/kg/store/store.go
+++ b/core/kg/store/store.go
@@ -12,4 +12,5 @@
Save(user *model.User) (*model.User, error)
Get(id string) (*model.User, error)
GetAll() ([]*model.User, error)
+ Count() (int64, error)
}