package main import ( "crypto/rand" "database/sql" "encoding/hex" "io" "net/http" "time" "golang.org/x/crypto/bcrypt" dbtypes "git.andr3h3nriqu3s.com/andr3/fyp/logic/db_types" "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils" . "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils" ) func generateSalt() string { salt := make([]byte, 4) _, err := io.ReadFull(rand.Reader, salt) if err != nil { panic("TODO handle this better") } return hex.EncodeToString(salt) } func hashPassword(password string, salt string) (string, error) { bytes_salt, err := hex.DecodeString(salt) if err != nil { return "", err } bytes, err := bcrypt.GenerateFromPassword(append([]byte(password), bytes_salt...), 14) return string(bytes), err } func genToken() string { token := make([]byte, 60) _, err := io.ReadFull(rand.Reader, token) if err != nil { panic("TODO handle this better") } return hex.EncodeToString(token) } func generateToken(db *sql.DB, email string, password string) (string, bool) { row, err := db.Query("select id, salt, password from users where email = $1;", email) if err != nil || !row.Next() { return "", false } var db_id string var db_salt string var db_password string err = row.Scan(&db_id, &db_salt, &db_password) if err != nil { return "", false } bytes_salt, err := hex.DecodeString(db_salt) if err != nil { panic("TODO handle better! Somethign is wrong with salt being stored in the database") } if err = bcrypt.CompareHashAndPassword([]byte(db_password), append([]byte(password), bytes_salt...)); err != nil { return "", false } token := genToken() _, err = db.Exec("insert into tokens (user_id, token) values ($1, $2);", db_id, token) if err != nil { return "", false } return token, true } func usersEndpints(db *sql.DB, handle *Handle) { handle.GetHTML("/login", AnswerTemplate("login.html", nil, 0)) handle.Post("/login", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { if c.Mode == JSON { type UserLogin struct { Email string `json:"email"` Password string `json:"password"` } var dat UserLogin if err := c.ToJSON(r, &dat); err != nil { return err } // TODO Give this to the generateToken function token, login := generateToken(db, dat.Email, dat.Password) if !login { return c.SendJSONStatus(http.StatusUnauthorized, "Email or password are incorrect") } user, err := dbtypes.UserFromToken(c.Db, token) if err != nil { return c.Error500(err) } type UserReturn struct { Token string `json:"token"` Id string `json:"id"` UserType int `json:"user_type"` Username string `json:"username"` Email string `json:"email"` } userReturn := UserReturn{ Token: token, Id: user.Id, UserType: user.UserType, Username: user.Username, Email: user.Email, } return c.SendJSON(userReturn) } r.ParseForm() f := r.Form if CheckEmpty(f, "email") || CheckEmpty(f, "password") { LoadBasedOnAnswer(c.Mode, w, "login.html", c.AddMap(AnyMap{ "Submited": true, })) return nil } email := f.Get("email") password := f.Get("password") // TODO Give this to the generateToken function expiration := time.Now().Add(24 * time.Hour) token, login := generateToken(db, email, password) if !login { LoadBasedOnAnswer(c.Mode, w, "login.html", c.AddMap(AnyMap{ "Submited": true, "NoUserOrPassword": true, "Email": email, })) return nil } cookie := &http.Cookie{Name: "auth", Value: token, HttpOnly: false, Expires: expiration} http.SetCookie(w, cookie) w.Header().Set("Location", "/") w.WriteHeader(http.StatusSeeOther) return nil }) handle.GetHTML("/register", AnswerTemplate("register.html", nil, 0)) handle.Post("/register", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { if c.Mode == JSON { type UserLogin struct { Username string `json:"username"` Email string `json:"email"` Password string `json:"password"` } var dat UserLogin if err := c.ToJSON(r, &dat); err != nil { return err } if len(dat.Username) == 0 || len(dat.Password) == 0 || len(dat.Email) == 0 { return c.SendJSONStatus(http.StatusBadRequest, "Please provide a valid json") } rows, err := db.Query("select username, email from users where username=$1 or email=$2;", dat.Username, dat.Email) if err != nil { return c.Error500(err) } defer rows.Close() if rows.Next() { var db_username string var db_email string err = rows.Scan(&db_username, &db_email) if err != nil { return c.Error500(err) } if db_email == dat.Email { return c.SendJSONStatus(http.StatusBadRequest, "Email already in use!") } if db_username == dat.Username { return c.SendJSONStatus(http.StatusBadRequest, "Username already in use!") } panic("Unrechable") } if len([]byte(dat.Password)) > 68 { return c.JsonBadRequest("Password is to long!") } salt := generateSalt() hash_password, err := hashPassword(dat.Password, salt) if err != nil { return c.Error500(err) } _, err = db.Exec("insert into users (username, email, salt, password) values ($1, $2, $3, $4);", dat.Username, dat.Email, salt, hash_password) if err != nil { return c.Error500(err) } // TODO Give this to the generateToken function token, login := generateToken(db, dat.Email, dat.Password) if !login { return c.SendJSONStatus(500, "Could not login after creatting account please try again later") } user, err := dbtypes.UserFromToken(c.Db, token) if err != nil { return c.Error500(err) } type UserReturn struct { Token string `json:"token"` Id string `json:"id"` UserType int `json:"user_type"` Username string `json:"username"` Email string `json:"email"` } userReturn := UserReturn{ Token: token, Id: user.Id, UserType: user.UserType, Username: user.Username, Email: user.Email, } return c.SendJSON(userReturn) } r.ParseForm() f := r.Form if CheckEmpty(f, "email") || CheckEmpty(f, "password") || CheckEmpty(f, "username") { LoadBasedOnAnswer(c.Mode, w, "register.html", AnyMap{ "Submited": true, }) return nil } email := f.Get("email") username := f.Get("username") password := f.Get("password") rows, err := db.Query("select username, email from users where username=$1 or email=$2;", username, email) if err != nil { panic("TODO handle this") } defer rows.Close() if rows.Next() { var db_username string var db_email string err = rows.Scan(&db_username, &db_email) if err != nil { panic("TODO handle this better") } LoadBasedOnAnswer(c.Mode, w, "register.html", AnyMap{ "Submited": true, "Email": email, "Username": username, "EmailError": db_email == email, "UserError": db_username == username, }) return nil } if len([]byte(password)) > 68 { LoadBasedOnAnswer(c.Mode, w, "register.html", AnyMap{ "Submited": true, "Email": email, "Username": username, "PasswordToLong": true, }) return nil } salt := generateSalt() hash_password, err := hashPassword(password, salt) if err != nil { return &Error{ Code: http.StatusInternalServerError, } } _, err = db.Exec("insert into users (username, email, salt, password) values ($1, $2, $3, $4);", username, email, salt, hash_password) if err != nil { return &Error{ Code: http.StatusInternalServerError, } } // TODO Give this to the generateToken function expiration := time.Now().Add(24 * time.Hour) token, login := generateToken(db, email, password) if !login { msg := "Login failed" return &Error{ Code: http.StatusInternalServerError, Msg: &msg, } } cookie := &http.Cookie{Name: "auth", Value: token, HttpOnly: false, Expires: expiration} http.SetCookie(w, cookie) w.Header().Set("Location", "/") w.WriteHeader(http.StatusSeeOther) return nil }) handle.Get("/user/info", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { if !CheckAuthLevel(1, w, r, c) { return nil } if c.Mode == JSON { return c.Error500(nil) } LoadBasedOnAnswer(c.Mode, w, "users/edit.html", c.AddMap(AnyMap{ "Email": c.User.Email, })) return nil }) // Handles updating users handle.Post("/user/info", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { if !CheckAuthLevel(int(dbtypes.User_Normal), w, r, c) { return nil } if c.Mode != JSON { return c.Error500(nil) } type UserData struct { Id string `json:"id"` Email string `json:"email"` } var dat UserData if err := c.ToJSON(r, &dat); err != nil { return err } if dat.Id != c.User.Id && c.User.UserType != int(dbtypes.User_Admin) { return c.SendJSONStatus(403, "You need to be an admin to update another users account") } if dat.Id != c.User.Id { var data struct { Id string } err := utils.GetDBOnce(c, &data, "users where id=$1", dat.Id) if err == NotFoundError { return c.JsonBadRequest("User does not exist") } else if err != nil { return c.Error500(err) } } var data struct { Id string } err := utils.GetDBOnce(c, &data, "users where email=$1", dat.Email) if err != nil && err != NotFoundError { return c.Error500(err) } if err != NotFoundError { if data.Id == dat.Id { return c.JsonBadRequest("Email is the name as the previous one!") } else { return c.JsonBadRequest("Email already in use") } } _, err = c.Db.Exec("update users set email=$2 where id=$1", dat.Id, dat.Email) if err != nil { return c.Error500(err) } var user struct { Id string Username string Email string User_Type int } err = utils.GetDBOnce(c, &user, "users where id=$1", dat.Id) if err != nil { return c.Error500(err) } toReturnUser := dbtypes.User{ Id: user.Id, Username: user.Username, Email: user.Email, UserType: user.User_Type, } return c.SendJSON(toReturnUser) }) handle.Post("/user/info/email", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { if !CheckAuthLevel(1, w, r, c) { return nil } if c.Mode == JSON { return c.Error500(nil) } r.ParseForm() if CheckEmpty(r.Form, "email") { return c.Error400(nil, "Email Not provided", w, "users/edit.html", "mainbody", c.AddMap(AnyMap{ "Email": c.User.Email, })) } _, err := c.Db.Exec("update users set email=$1 where id=$2", r.Form.Get("email"), c.User.Id) if err != nil { return c.Error500(err) } LoadBasedOnAnswer(c.Mode, w, "users/edit.html", c.AddMap(AnyMap{ "Email": r.Form.Get("email"), })) return nil }) handle.Post("/user/info/password", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { if !CheckAuthLevel(1, w, r, c) { return nil } if c.Mode == JSON { var dat struct { Old_Password string `json:"old_password"` Password string `json:"password"` Password2 string `json:"password2"` } if err := c.ToJSON(r, &dat); err != nil { return err } if dat.Password == "" { return c.JsonBadRequest("Password can not be empty") } if dat.Password != dat.Password2 { return c.JsonBadRequest("New passwords did not match") } c.Logger.Warn("test", "dat", dat) _, login := generateToken(db, c.User.Email, dat.Old_Password) if !login { return c.JsonBadRequest("Password is incorrect"); } salt := generateSalt() hash_password, err := hashPassword(dat.Password, salt) if err != nil { return c.Error500(err) } _, err = db.Exec("update users set salt=$1, password=$2 where id=$3", salt, hash_password, c.User.Id) if err != nil { return c.Error500(err) } return c.SendJSON(c.User.Id) } r.ParseForm() f := r.Form if CheckEmpty(f, "old_password") || CheckEmpty(f, "password") || CheckEmpty(f, "password2") { return c.Error400(nil, "OldPassword, Password or Password2 not provided!", w, "users/edit.html", "mainbody", c.AddMap(AnyMap{ "Email": c.User.Email, "NoUserOrPassword": true, })) } password := f.Get("password") password2 := f.Get("password2") if password != password2 { return c.Error400(nil, "New passwords did not match", w, "users/edit.html", "mainbody", c.AddMap(AnyMap{ "Email": c.User.Email, "PasswordNotTheSame": true, })) } _, login := generateToken(db, c.User.Email, f.Get("old_password")) if !login { return c.Error400(nil, "Password was incorrect", w, "users/edit.html", "mainbody", c.AddMap(AnyMap{ "Email": c.User.Email, "NoUserOrPassword": true, })) } salt := generateSalt() hash_password, err := hashPassword(password, salt) if err != nil { return c.Error500(err) } _, err = db.Exec("update users set salt=$1, password=$2 where id=$3", salt, hash_password, c.User.Id) if err != nil { return c.Error500(err) } LoadBasedOnAnswer(c.Mode, w, "users/edit.html", c.AddMap(AnyMap{ "email": c.User.Email, })) return nil }) handle.Get("/logout", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { if c.Mode == JSON { panic("TODO handle json") } Logoff(c.Mode, w, r) return nil }) }