From 734d4ec6f61a6ef69013c563b3fc72a9157812ac Mon Sep 17 00:00:00 2001 From: Erhan Yakut Date: Sat, 6 Jun 2020 22:55:35 +0300 Subject: [PATCH] added signup endpoint for web access --- internal/api/signup.go | 77 +++++++++++++++++++++++++++++++++++++++ internal/api/user.go | 26 +------------ internal/app/user.go | 21 +++++++++-- internal/router/router.go | 10 ++++- 4 files changed, 106 insertions(+), 28 deletions(-) create mode 100644 internal/api/signup.go diff --git a/internal/api/signup.go b/internal/api/signup.go new file mode 100644 index 0000000..85d3f0d --- /dev/null +++ b/internal/api/signup.go @@ -0,0 +1,77 @@ +package api + +import ( + "encoding/json" + "net/http" + + "github.com/go-playground/validator/v10" + "github.com/pass-wall/passwall-server/internal/app" + "github.com/pass-wall/passwall-server/internal/storage" + "github.com/pass-wall/passwall-server/model" +) + +const ( + SignupSuccess = "User created successfully" +) + +// Signup ... +func Signup(s storage.Store) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + userDTO := new(model.UserDTO) + + // 1. Decode request body to userDTO object + decoder := json.NewDecoder(r.Body) + if err := decoder.Decode(&userDTO); err != nil { + RespondWithError(w, http.StatusBadRequest, "Invalid resquest payload") + return + } + defer r.Body.Close() + + // 2. Run validator according to model.UserDTO validator tags + validate := validator.New() + validateError := validate.Struct(userDTO) + if validateError != nil { + errs := GetErrors(validateError.(validator.ValidationErrors)) + RespondWithErrors(w, http.StatusBadRequest, InvalidRequestPayload, errs) + return + } + + // 3. Check if user exist in database + _, err := s.Users().FindByEmail(userDTO.Email) + if err == nil { + errs := []string{"This email is already used!"} + message := "User couldn't created!" + RespondWithErrors(w, http.StatusBadRequest, message, errs) + return + } + + // 4. Create new user + createdUser, err := app.CreateUser(s, userDTO) + if err != nil { + RespondWithError(w, http.StatusInternalServerError, err.Error()) + return + } + + // 5. Update user once to generate schema + updatedUser, err := app.UpdateUser(s, createdUser, userDTO, false) + if err != nil { + RespondWithError(w, http.StatusInternalServerError, err.Error()) + return + } + + // 6. Migrate user specific tables in user's schema + err = s.Users().Migrate(updatedUser.Schema) + if err != nil { + RespondWithError(w, http.StatusInternalServerError, err.Error()) + return + } + + response := model.Response{ + Code: http.StatusOK, + Status: Success, + Message: SignupSuccess, + } + RespondWithJSON(w, http.StatusOK, response) + } +} diff --git a/internal/api/user.go b/internal/api/user.go index acceab8..b13cf88 100644 --- a/internal/api/user.go +++ b/internal/api/user.go @@ -2,7 +2,6 @@ package api import ( "encoding/json" - "fmt" "net/http" "strconv" @@ -10,7 +9,6 @@ import ( "github.com/pass-wall/passwall-server/internal/app" "github.com/pass-wall/passwall-server/internal/storage" "github.com/pass-wall/passwall-server/model" - uuid "github.com/satori/go.uuid" "github.com/gorilla/mux" ) @@ -94,28 +92,14 @@ func CreateUser(s storage.Store) http.HandlerFunc { } // 4. Create new user - // Hasing the master password with SHA256 - userDTO.MasterPassword = app.NewSHA256([]byte(userDTO.MasterPassword)) - - // All new users are free member and Member (not Admin) - userDTO.Plan = "Free" - userDTO.Role = "Member" - - // Generate new UUID for user - userDTO.UUID = uuid.NewV4() - createdUser, err := app.CreateUser(s, userDTO) if err != nil { RespondWithError(w, http.StatusInternalServerError, err.Error()) return } - // 5. Generate Schema and update user Schema field - // TODO: I am not sure if we need this schema field - isAuthorized := r.Context().Value("authorized").(bool) - userDTO.Schema = fmt.Sprintf("user%d", createdUser.ID) - fmt.Println(userDTO.Schema) - updatedUser, err := app.UpdateUser(s, createdUser, userDTO, isAuthorized) + // 5. Update user once to generate schema + updatedUser, err := app.UpdateUser(s, createdUser, userDTO, true) if err != nil { RespondWithError(w, http.StatusInternalServerError, err.Error()) return @@ -132,7 +116,6 @@ func CreateUser(s storage.Store) http.HandlerFunc { } } -// TODO: // UpdateUser ... func UpdateUser(s storage.Store) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { @@ -161,11 +144,6 @@ func UpdateUser(s storage.Store) http.HandlerFunc { return } - // claims := token.Claims.(jwt.MapClaims) - // fmt.Printf("Token for user %v expires %v", claims["user"], claims["exp"]) - - // atClaims["authorized"] = true - // Check if user exist in database with new email address if userDTO.Email != user.Email { _, err := s.Users().FindByEmail(userDTO.Email) diff --git a/internal/app/user.go b/internal/app/user.go index a1819ce..bd7dd04 100644 --- a/internal/app/user.go +++ b/internal/app/user.go @@ -1,14 +1,27 @@ package app import ( + "fmt" + "github.com/pass-wall/passwall-server/internal/storage" "github.com/pass-wall/passwall-server/model" + uuid "github.com/satori/go.uuid" ) // CreateUser creates a user and saves it to the store -func CreateUser(s storage.Store, dto *model.UserDTO) (*model.User, error) { +func CreateUser(s storage.Store, userDTO *model.UserDTO) (*model.User, error) { + + // Hasing the master password with SHA256 + userDTO.MasterPassword = NewSHA256([]byte(userDTO.MasterPassword)) + + // New user's plan is Free and role is Member (not Admin) + userDTO.Plan = "Free" + userDTO.Role = "Member" - createdUser, err := s.Users().Save(model.ToUser(dto)) + // Generate new UUID for user + userDTO.UUID = uuid.NewV4() + + createdUser, err := s.Users().Save(model.ToUser(userDTO)) if err != nil { return nil, err } @@ -27,7 +40,9 @@ func UpdateUser(s storage.Store, user *model.User, userDTO *model.UserDTO, isAut user.Name = userDTO.Name user.Email = userDTO.Email user.MasterPassword = userDTO.MasterPassword - user.Schema = userDTO.Schema + + // This never changes + user.Schema = fmt.Sprintf("user%d", user.ID) // Only Admin's can change plan and role if isAuthorized { diff --git a/internal/router/router.go b/internal/router/router.go index 4597b0c..df5a31e 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -96,6 +96,10 @@ func (r *Router) initRoutes() { authRouter.HandleFunc("/refresh", api.RefreshToken(r.store)) authRouter.HandleFunc("/check", api.CheckToken(r.store)) + // Web public endpoints + webRouter := mux.NewRouter().PathPrefix("/web").Subrouter() + webRouter.HandleFunc("/signup", api.Signup(r.store)).Methods(http.MethodPost) + n := negroni.Classic() n.Use(negroni.HandlerFunc(CORS)) n.Use(negroni.HandlerFunc(Secure)) @@ -106,11 +110,15 @@ func (r *Router) initRoutes() { )) r.router.PathPrefix("/auth").Handler(n.With( - LimitHandler(), negroni.Wrap(authRouter), )) + r.router.PathPrefix("/web").Handler(n.With( + LimitHandler(), + negroni.Wrap(webRouter), + )) + // Insecure endpoints r.router.HandleFunc("/health", api.HealthCheck(r.store)).Methods(http.MethodGet)