From 00f4288c254002faefe56785e38295373ba04122 Mon Sep 17 00:00:00 2001 From: Lukas Bachschwell Date: Tue, 13 Nov 2018 21:39:04 +0100 Subject: [PATCH] Register endpoint working --- controllers/auth.go | 196 ++++++++++++++++++++++---------- controllers/base.go | 11 ++ models/userCompanyMap.go | 2 +- services/companydb/companydb.go | 87 ++++++++------ 4 files changed, 206 insertions(+), 90 deletions(-) diff --git a/controllers/auth.go b/controllers/auth.go index 7fb680a..e1d0f07 100644 --- a/controllers/auth.go +++ b/controllers/auth.go @@ -1,6 +1,7 @@ package controllers import ( + "fmt" "multitenantStack/models" companydb "multitenantStack/services/companydb" @@ -9,6 +10,7 @@ import ( "github.com/astaxie/beego/orm" jwt "github.com/dgrijalva/jwt-go" + "github.com/kennygrant/sanitize" ) // AuthController operations for Auth @@ -16,14 +18,18 @@ type AuthController struct { BaseController } +// AuthResponse the format for all responses from auth +type AuthResponse struct { + Status int `json:"status"` + Jwt string `json:"jwt"` + User models.CompanyUser `json:"user"` +} + // URLMapping ... func (c *AuthController) URLMapping() { // This block is used to drastically speed up the annotation -> lookup process c.Mapping("Login", c.Login) - c.Mapping("GetOne", c.GetOne) - c.Mapping("GetAll", c.GetAll) - c.Mapping("Put", c.Put) - c.Mapping("Delete", c.Delete) + c.Mapping("Register", c.Register) } // Login Get a JWT token for the user @@ -34,13 +40,6 @@ func (c *AuthController) URLMapping() { // @Failure 403 body is empty // @router /login [post] func (c *AuthController) Login() { - - type AuthResponse struct { - Status int `json:"status"` - Jwt string `json:"jwt"` - User models.CompanyUser `json:"user"` - } - if c.Ctx.Input.Method() != "POST" { c.ServeJSONError("Method not allowed") return @@ -127,52 +126,135 @@ func (c *AuthController) Login() { c.ServeJSON() } -// GetOne ... -// @Title GetOne -// @Description get Auth by id -// @Param id path string true "The key for staticblock" -// @Success 200 {object} models.Auth -// @Failure 403 :id is empty -// @router /:id [get] -func (c *AuthController) GetOne() { - -} - -// GetAll ... -// @Title GetAll -// @Description get Auth -// @Param query query string false "Filter. e.g. col1:v1,col2:v2 ..." -// @Param fields query string false "Fields returned. e.g. col1,col2 ..." -// @Param sortby query string false "Sorted-by fields. e.g. col1,col2 ..." -// @Param order query string false "Order corresponding to each sortby field, if single value, apply to all sortby fields. e.g. desc,asc ..." -// @Param limit query string false "Limit the size of result set. Must be an integer" -// @Param offset query string false "Start position of result set. Must be an integer" -// @Success 200 {object} models.Auth -// @Failure 403 -// @router / [get] -func (c *AuthController) GetAll() { - -} - -// Put ... -// @Title Put -// @Description update the Auth -// @Param id path string true "The id you want to update" +// Register Register a new company and user, create DB and so on +// @Title Create +// @Description create Auth // @Param body body models.Auth true "body for Auth content" -// @Success 200 {object} models.Auth -// @Failure 403 :id is not int -// @router /:id [put] -func (c *AuthController) Put() { - -} - -// Delete ... -// @Title Delete -// @Description delete the Auth -// @Param id path string true "The id you want to delete" -// @Success 200 {string} delete success! -// @Failure 403 id is empty -// @router /:id [delete] -func (c *AuthController) Delete() { +// @Success 201 {object} models.Auth +// @Failure 403 body is empty +// @router /register [post] +func (c *AuthController) Register() { + // can be called without jwt token set + + // needed data: + // email + // password + // companyname + // optional: username + + // Tasks: + // create database + // create user_company_map entry + // create company user + // return jwt token and user profile + + if c.Ctx.Input.Method() != "POST" { + c.ServeJSONError("Method not allowed") + return + } + + tokenHeader := c.Ctx.Request.Header.Get("X-JWTtoken") + if tokenHeader != "" { + valid, _ := tokenTools.Validate(tokenHeader) + if valid { + c.ServeJSONError("You are already logged in") + return + } + } + + email := c.GetString("email") + password := c.GetString("password") + username := c.GetString("username") + companyname := c.GetString("companyname") + + companyname = sanitize.BaseName(companyname) + companyname = fmt.Sprintf("company_%s", companyname) + + if email == "" || password == "" || companyname == "" || username == "" { + c.ServeJSONError("Email/Password/Companyname missing") + return + } + + systemdb := companydb.GetSystemDatabase() + + if systemdb == nil { + c.ServeJSONError("Error retrieving data") + return + } + + o, err := orm.NewOrmWithDB("postgres", "default", systemdb) + if err != nil { + c.ServeJSONError("Error retrieving data") + return + } + + ucmExists, err := models.GetUserCompanyMapByEmail(o, email) + if err != nil && ucmExists != nil { + c.ServeJSONError("Error: Email exists!") + return + } + + if companydb.HasDatabase(companyname) { + c.ServeJSONError("Error: Company exists!") + return + } + + var userCompanyMapping models.UserCompanyMap + newHash, _ := tokenTools.HashPassword(password) + userCompanyMapping.PasswordHash = newHash + userCompanyMapping.Company = companyname + userCompanyMapping.Email = email + + ucmID, err := models.AddUserCompanyMap(o, &userCompanyMapping) + if err != nil { + c.ServeJSONErrorWithError("Error on saving user", err) + return + } + + // Create company + newDB, err := companydb.CreateDatabase(companyname) + if err != nil { + c.ServeJSONErrorWithError("Error on creating DB", err) + return + } + + newO, err := orm.NewOrmWithDB("postgres", "default", newDB) + if err != nil { + c.ServeJSONErrorWithError("Error retrieving company data", err) + return + } + + var companyUser models.CompanyUser + companyUser.Name = username + companyUser.Profile = "{}" + companyUser.Role = 1 //TODO: replacxe with owner constant + + userID, err := models.AddCompanyUser(newO, &companyUser) + if err != nil { + c.ServeJSONError("Error on saving company user") + return + } + + // edit usermapping + userCompanyMapping.ID = int(ucmID) + userCompanyMapping.CompanyUserID = int16(userID) + if err := models.UpdateUserCompanyMapById(o, &userCompanyMapping); err != nil { + c.ServeJSONError("Error on saving user") + return + } + + tokenString := "" + + tokenString = tokenTools.CreateToken(jwt.MapClaims{ + "email": email, + "companyName": companyname, + "companyUserID": userCompanyMapping.CompanyUserID, + "exp": time.Now().Unix() + 3600, + }) + + json := AuthResponse{200, tokenString, companyUser} + c.Data["json"] = &json + + c.ServeJSON() } diff --git a/controllers/base.go b/controllers/base.go index 417dccd..def1e44 100644 --- a/controllers/base.go +++ b/controllers/base.go @@ -1,6 +1,8 @@ package controllers import ( + "fmt" + "github.com/astaxie/beego" ) @@ -39,6 +41,15 @@ func (c *BaseController) ServeJSONErrorWithCode(errorcode int, message string) { c.ServeJSON() } +// ServeJSONErrorWithError respond with a JSON error and print an error message +func (c *BaseController) ServeJSONErrorWithError(message string, err error) { + message = fmt.Sprintf("%s %s", message, err.Error()) + json := JSONBasicResponse{JSONError, message} + c.Data["json"] = &json + ///c.Ctx.ResponseWriter.WriteHeader(400) + c.ServeJSON() +} + // ServeJSONSuccess respond with a JSON success message func (c *BaseController) ServeJSONSuccess(message string) { json := JSONBasicResponse{JSONSuccess, message} diff --git a/models/userCompanyMap.go b/models/userCompanyMap.go index f891643..e762991 100644 --- a/models/userCompanyMap.go +++ b/models/userCompanyMap.go @@ -11,7 +11,7 @@ import ( ) type UserCompanyMap struct { - ID int `orm:"column(id);pk"` + ID int `orm:"column(id)"` // removed pk here to fix postgres error Email string `orm:"column(email)"` PasswordHash string `orm:"column(password_hash)"` Company string `orm:"column(company)"` diff --git a/services/companydb/companydb.go b/services/companydb/companydb.go index ab9a69a..1008bac 100644 --- a/services/companydb/companydb.go +++ b/services/companydb/companydb.go @@ -68,8 +68,6 @@ func GetDatabaseWithName(companyName string) (*sql.DB, error) { return db, nil } -// TODO: call upper function in this one to reduce code - // GetDatabase Get orm and user information func GetDatabase(tokenString string) (jwt.MapClaims, *sql.DB, error) { // validate token @@ -86,9 +84,7 @@ func GetDatabase(tokenString string) (jwt.MapClaims, *sql.DB, error) { return tokenMap, dbs[companyName], nil } - conStr := fmt.Sprintf("host=127.0.0.1 port=5435 user=postgres password=postgre dbname=%s sslmode=disable", companyName) - fmt.Println(conStr) - db, err := sql.Open("postgres", conStr) + db, err := GetDatabaseWithName(companyName) if err != nil { return nil, nil, err } @@ -97,36 +93,63 @@ func GetDatabase(tokenString string) (jwt.MapClaims, *sql.DB, error) { return tokenMap, db, nil } -// CreateDatabase Create a database by copying the template -func CreateDatabase(companyName string) { - /* - if dbs[companyName] != nil { - fmt.Println("DB Already open") - return dbs[companyName], nil - } - /* - db, err = sql.Open("postgres", "host=127.0.0.1 port=5435 user=postgres password=postgre dbname=company5 sslmode=disable") - if err != nil { - log.Fatal(err) - } +// HasDatabase Check if DB exists +func HasDatabase(dbname string) bool { + systemDB := GetSystemDatabase() + result, err := systemDB.Query("SELECT datname FROM pg_database WHERE datistemplate = false;") + if err != nil { + return false + } - or, err := orm.NewOrmWithDB("postgres", "temp", db) - */ + for result.Next() { + var aDbName string + result.Scan(&aDbName) + if aDbName == dbname { + return true + } + } + + return false +} + +// CreateDatabase Create a database by copying the template +func CreateDatabase(companyName string) (*sql.DB, error) { + if HasDatabase(companyName) { + return nil, errors.New("DB already exists") + } + + systemDB := GetSystemDatabase() + // Takes about 1.2 seconds and we trust companyName to be sanitized in register + queryString := fmt.Sprintf("CREATE DATABASE %s TEMPLATE company_template;", companyName) + _, err := systemDB.Exec(queryString) + if err != nil { + return nil, err + } + + db, err := GetDatabaseWithName(companyName) + if err != nil { + return nil, err + } + + return db, nil } // DeleteDatabase Delete an entire database, this is very very dangerous :-) -func DeleteDatabase(token string) { - //_, db, err := GetDatabase(token) - /* - remove from map! - db.Close() - fmt.Println("Closed company5") - //} +func DeleteDatabase(companyName string) error { + if !HasDatabase(companyName) { + return errors.New("DB does not exist") + } + systemDB := GetSystemDatabase() - res, err := o.Raw("DROP DATABASE company5;").Exec() - if err == nil { - num, _ := res.RowsAffected() - fmt.Println("mysql row affected number: ", num) - } - */ + db, err := GetDatabaseWithName(companyName) + db.Close() + delete(dbs, companyName) + fmt.Println("Closed %s", companyName) + + queryString := fmt.Sprintf("DROP DATABASE %s;", companyName) + _, err = systemDB.Exec(queryString) + if err != nil { + return err + } + return nil }