Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Signup endpoint #320

Merged
merged 9 commits into from
Sep 4, 2022
4 changes: 3 additions & 1 deletion conf/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@
"MAX_MESSAGE_SIZE_MB": 12,
"SHOWABLE_ERROR_STATUS_CODE": 666,
"ANALYTICS_TOKEN": "+KoOqVQu+cSF+qW4owTUdoicAzzRoICI5LcM3o5CMLCyA3cqK6e1yef6r2eqrAiYwJv7Z2r3gByZlBPTAfINPN6vQpRXFaxY60FV8GYi",
"POISON_MSGS_RETENTION_IN_HOURS": 3
"POISON_MSGS_RETENTION_IN_HOURS": 3,
"MAILCHIMP_KEY": "50afc29f5c1fda04bf10bdc4009a986c-us9",
"MAILCHIMP_LIST_ID": "506751f7f5"
}
4 changes: 3 additions & 1 deletion conf/docker-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@
"MAX_MESSAGE_SIZE_MB": 12,
"SHOWABLE_ERROR_STATUS_CODE": 666,
"ANALYTICS_TOKEN": "+KoOqVQu+cSF+qW4owTUdoicAzzRoICI5LcM3o5CMLCyA3cqK6e1yef6r2eqrAiYwJv7Z2r3gByZlBPTAfINPN6vQpRXFaxY60FV8GYi",
"POISON_MSGS_RETENTION_IN_HOURS": 3
"POISON_MSGS_RETENTION_IN_HOURS": 3,
"MAILCHIMP_KEY": "50afc29f5c1fda04bf10bdc4009a986c-us9",
"MAILCHIMP_LIST_ID": "506751f7f5"
}
2 changes: 2 additions & 0 deletions http_server/routes/user_mgmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ func InitializeUserMgmtRoutes(router *gin.RouterGroup) {
userMgmtRoutes.POST("/doneNextSteps", userMgmtHandler.DoneNextSteps)
userMgmtRoutes.POST("/refreshToken", userMgmtHandler.RefreshToken)
userMgmtRoutes.POST("/addUser", userMgmtHandler.AddUser)
userMgmtRoutes.POST("/addUserSignUp", userMgmtHandler.AddUserSignUp)
userMgmtRoutes.GET("/getSignUpFlag", userMgmtHandler.GetSignUpFlag)
userMgmtRoutes.GET("/getAllUsers", userMgmtHandler.GetAllUsers)
userMgmtRoutes.DELETE("/removeUser", userMgmtHandler.RemoveUser)
userMgmtRoutes.DELETE("/removeMyUser", userMgmtHandler.RemoveMyUser)
Expand Down
4 changes: 3 additions & 1 deletion middlewares/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import (
var noNeedAuthRoutes = []string{
"/api/usermgmt/login",
"/api/usermgmt/refreshtoken",
"/api/usermgmt/addusersignup",
"/api/usermgmt/getsignupflag",
"/api/status",
"/api/sandbox/login",
}
Expand All @@ -52,7 +54,7 @@ func isAuthNeeded(path string) bool {
return false
}

for _, route := range noNeedAuthRoutes {
for _, route := range noNeedAuthRoutes {
if route == path {
return false
}
Expand Down
16 changes: 10 additions & 6 deletions models/user_mgmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ type User struct {
AlreadyLoggedIn bool `json:"already_logged_in" bson:"already_logged_in"`
CreationDate time.Time `json:"creation_date" bson:"creation_date"`
AvatarId int `json:"avatar_id" bson:"avatar_id"`
FullName string `json:"full_name" bson:"full_name"`
Subscribtion bool `json:"subscription" bson:"subscription"`
}

type Image struct {
Expand All @@ -46,12 +48,14 @@ type Image struct {
}

type AddUserSchema struct {
Username string `json:"username" binding:"required,min=1,max=25"`
Password string `json:"password"`
HubUsername string `json:"hub_username"`
HubPassword string `json:"hub_password"`
UserType string `json:"user_type" binding:"required"`
AvatarId int `json:"avatar_id"`
Username string `json:"username" binding:"required,min=1,max=25"`
shohamroditimemphis marked this conversation as resolved.
Show resolved Hide resolved
Password string `json:"password"`
HubUsername string `json:"hub_username"`
HubPassword string `json:"hub_password"`
UserType string `json:"user_type" binding:"required"`
AvatarId int `json:"avatar_id"`
FullName string `json:"full_name"`
Subscribtion bool `json:"subscription"`
}

type AuthenticateNatsSchema struct {
Expand Down
178 changes: 178 additions & 0 deletions server/memphis_handlers_user_mgmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ package server
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
Expand All @@ -40,13 +41,20 @@ import (

"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"github.com/hanzoai/gochimp3"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"golang.org/x/crypto/bcrypt"
)

type UserMgmtHandler struct{}
type MailChimpErr struct {
Title string `json:"title"`
Status int `json:"status"`
Detail string `json:"detail"`
Instance string `json:"instance"`
}

func isRootUserExist() (bool, error) {
filter := bson.M{"user_type": "root"}
Expand All @@ -60,6 +68,28 @@ func isRootUserExist() (bool, error) {
return true, nil
}

func isOnlyRootUserExist() (bool, error) {
if configuration.SANDBOX_ENV == "true" {
return false, nil
}
cursor, err := usersCollection.Find(context.TODO(), bson.M{})
if err == mongo.ErrNoDocuments {
return false, nil
} else if err != nil {
return false, err
}
var users []models.User
if err = cursor.All(context.TODO(), &users); err != nil {
serv.Errorf("isOnlyRootUserExist error: " + err.Error())
return false, err
}
if len(users) == 1 && users[0].UserType == "root" {
return true, nil
} else {
return false, nil
}
}

func authenticateUser(username string, password string) (bool, models.User, error) {
filter := bson.M{"username": username}
var user models.User
Expand Down Expand Up @@ -155,6 +185,15 @@ func validateUsername(username string) error {
return nil
}

func validateEmail(email string) error {
re := regexp.MustCompile("^[a-z0-9._%+-]+@[a-z0-9_.-]+.[a-z]{2,4}$")
validateEmail := re.MatchString(email)
if !validateEmail || len(email) == 0 {
return errors.New("email is not valid")
}
return nil
}

type userToTokens interface {
models.User | models.SandboxUser
}
Expand Down Expand Up @@ -402,6 +441,145 @@ func (umh UserMgmtHandler) AuthenticateNatsUser(c *gin.Context) {
c.IndentedJSON(200, gin.H{})
}

func (umh UserMgmtHandler) GetSignUpFlag(c *gin.Context) {
exist, err := isOnlyRootUserExist()
if err != nil {
serv.Errorf("GetSignUpFlag error: " + err.Error())
c.AbortWithStatusJSON(500, gin.H{"message": "Server error"})
return
}
c.IndentedJSON(200, gin.H{"exist": exist})
}

func (umh UserMgmtHandler) AddUserSignUp(c *gin.Context) {
exist, err := isOnlyRootUserExist()
if err != nil {
serv.Errorf("CreateUserSignUp error: " + err.Error())
c.AbortWithStatusJSON(500, gin.H{"message": "Server error"})
return
}
if !exist {
c.IndentedJSON(401, gin.H{"message": "More than root user exists"})
shohamroditimemphis marked this conversation as resolved.
Show resolved Hide resolved
return
} else {
serv.Warnf("Only root user exists")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need it

Copy link
Contributor

@idanasulin2706 idanasulin2706 Sep 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

var body models.AddUserSchema
ok := utils.Validate(c, &body, false, nil)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

schema should be validated first thing in this function, as happens in other handlers

Copy link
Contributor

@idanasulin2706 idanasulin2706 Sep 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

if !ok {
return
}
username := strings.ToLower(body.Username)
usernameError := validateEmail(username)
if usernameError != nil {
serv.Warnf(usernameError.Error())
c.AbortWithStatusJSON(configuration.SHOWABLE_ERROR_STATUS_CODE, gin.H{"message": usernameError.Error()})
return
}
fullName := strings.ToLower(body.FullName)

hashedPwd, err := bcrypt.GenerateFromPassword([]byte(body.Password), bcrypt.MinCost)
if err != nil {
serv.Errorf("CreateUserSignUp error: " + err.Error())
c.AbortWithStatusJSON(500, gin.H{"message": "Server error"})
return
}
hashedPwdString := string(hashedPwd)
subscription := body.Subscribtion

if subscription {
mailchimpClient := gochimp3.New(configuration.MAILCHIMP_KEY)
mailchimpListID := configuration.MAILCHIMP_LIST_ID
mailchimpList, err := mailchimpClient.GetList(mailchimpListID, nil)
if err != nil {
serv.Errorf("CreateUserSignUp error: " + err.Error())
}

mailchimpReq := &gochimp3.MemberRequest{
EmailAddress: username,
Status: "subscribed",
Tags: []string{"signup"},
shohamroditimemphis marked this conversation as resolved.
Show resolved Hide resolved
}
_, err = mailchimpList.CreateMember(mailchimpReq)
if err != nil {
data, err := json.Marshal(err)
var mailChimpErr MailChimpErr
if err = json.Unmarshal([]byte(data), &mailChimpErr); err != nil {
serv.Errorf("Error: " + err.Error())
}
mailChimpReqSearch := &gochimp3.SearchMembersQueryParams{
Query: body.Username,
}
if data != nil {
if mailChimpErr.Title == "Member Exists" && mailChimpErr.Status == 400 {
res, err := mailchimpList.SearchMembers(mailChimpReqSearch)
if err != nil {
serv.Errorf("Failed to search member in mailChimp: " + err.Error())
}
_, err = mailchimpList.UpdateMember(res.ExactMatches.Members[0].ID, mailchimpReq)
if err != nil {
serv.Errorf("Failed to update member in mailChimp: " + err.Error())
}
} else {
serv.Errorf("Failed to subscribe in mailChimp: " + err.Error())
}
}

}
}

newUser := models.User{
ID: primitive.NewObjectID(),
Username: username,
Password: hashedPwdString,
FullName: fullName,
Subscribtion: subscription,
UserType: "managment",
shohamroditimemphis marked this conversation as resolved.
Show resolved Hide resolved
CreationDate: time.Now(),
AlreadyLoggedIn: false,
AvatarId: 1,
}

_, err = usersCollection.InsertOne(context.TODO(), newUser)
if err != nil {
serv.Errorf("CreateUserSignUp error: " + err.Error())
c.AbortWithStatusJSON(500, gin.H{"message": "Server error"})
return
}

serv.Noticef("User " + username + " has been created")
token, refreshToken, err := CreateTokens(newUser)
if err != nil {
serv.Errorf("CreateUserSignUp error: " + err.Error())
c.AbortWithStatusJSON(500, gin.H{"message": "Server error"})
return
}
var env string
if configuration.DOCKER_ENV != "" {
env = "docker"
} else {
env = "K8S"
}

domain := ""
secure := false
c.SetCookie("jwt-refresh-token", refreshToken, configuration.REFRESH_JWT_EXPIRES_IN_MINUTES*60*1000, "/", domain, secure, true)
c.IndentedJSON(200, gin.H{
shohamroditimemphis marked this conversation as resolved.
Show resolved Hide resolved
"jwt": token,
"expires_in": configuration.JWT_EXPIRES_IN_MINUTES * 60 * 1000,
"user_id": newUser.ID,
"username": newUser.Username,
"user_type": newUser.UserType,
"creation_date": newUser.CreationDate,
"already_logged_in": newUser.AlreadyLoggedIn,
"avatar_id": newUser.AvatarId,
"send_analytics": "",
shohamroditimemphis marked this conversation as resolved.
Show resolved Hide resolved
"env": env,
"namespace": configuration.K8S_NAMESPACE,
})

}
}

func (umh UserMgmtHandler) AddUser(c *gin.Context) {
var body models.AddUserSchema
ok := utils.Validate(c, &body, false, nil)
Expand Down