package users import ( "crypto/rand" "encoding/hex" "io" "net/http" "time" "golang.org/x/crypto/bcrypt" "git.andr3h3nriqu3s.com/andr3/fyp/logic/db" . "git.andr3h3nriqu3s.com/andr3/fyp/logic/db_types" dbtypes "git.andr3h3nriqu3s.com/andr3/fyp/logic/db_types" . "git.andr3h3nriqu3s.com/andr3/fyp/logic/models" . "git.andr3h3nriqu3s.com/andr3/fyp/logic/tasks/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 deleteToken(db db.Db, userId string, time time.Time) (err error) { _, err = db.Exec("delete from tokens where emit_day=$1 and user_id=$2", time, userId) return } func generateToken(db db.Db, email string, password string, name 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, name) values ($1, $2, $3);", db_id, token, name) if err != nil { return "", false } return token, true } func DeleteUser(base BasePack, task Task) (err error) { ids, err := GetDbMultitple[JustId](base.GetDb(), "models where user_id=$1;", task.ExtraTaskInfo) if err != nil { task.UpdateStatusLog(base, TASK_FAILED_RUNNING, "Could not get models list") return } for i := range ids { err = DeleteModel(base, ids[i].Id) if err != nil { base.GetLogger().Error("Could not delete model", "err", err) } } _, err = base.GetDb().Exec("delete from users where id=$1", task.ExtraTaskInfo) if err != nil { task.UpdateStatusLog(base, TASK_FAILED_RUNNING, "Could not delete user") return } task.UpdateStatusLog(base, TASK_DONE, "User deleted with success") return } func UsersEndpints(db db.Db, handle *Handle) { type UserLogin struct { Email string `json:"email"` Password string `json:"password"` } PostAuthJson(handle, "/login", dbtypes.User_Not_Auth, func(c *Context, dat *UserLogin) *Error { // TODO Give this to the generateToken function token, login := generateToken(db, dat.Email, dat.Password, "Logged in user") if !login { return c.SendJSONStatus(http.StatusUnauthorized, "Email or password are incorrect") } user, err := dbtypes.UserFromToken(c.Db, token) if err != nil { return c.E500M("Failed to get user from token", err) } if user.UserType == int(User_Deleted) { return c.JsonBadRequest("Your user is in the process of being deleted!") } 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) }) type UserRegister struct { Username string `json:"username" validate:"required"` Email string `json:"email" validate:"required"` Password string `json:"password" validate:"required"` } PostAuthJson(handle, "/register", dbtypes.User_Not_Auth, func(c *Context, dat *UserRegister) *Error { var prevUser struct { Username string Email string } err := GetDBOnce(c, &prevUser, "users where username=$1 or email=$2;", dat.Username, dat.Email) if err == NotFoundError { // Do nothing the user does not exist and it's ok to create a new one } else if err != nil { return c.E500M("Falied to get user data", err) } else { if prevUser.Email == dat.Email { return c.SendJSONStatus(http.StatusBadRequest, "Email already in use!") } if prevUser.Username == dat.Username { return c.SendJSONStatus(http.StatusBadRequest, "Username already in use!") } } 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.E500M("Falied to store password", 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.E500M("Falied to create user", err) } // TODO Give this to the generateToken function token, login := generateToken(db, dat.Email, dat.Password, "User Login") 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.E500M("Falied to create user", 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) }) // TODO allow admin users to update this data handle.GetAuth("/user/info", dbtypes.User_Normal, func(c *Context) *Error { user, err := dbtypes.UserFromToken(c.Db, *c.Token) if err != nil { return c.E500M("Falied to get user data", err) } type UserReturn struct { Id string `json:"id"` UserType int `json:"user_type"` Username string `json:"username"` Email string `json:"email"` } userReturn := UserReturn{ Id: user.Id, UserType: user.UserType, Username: user.Username, Email: user.Email, } return c.SendJSON(userReturn) }) // Handles updating users type UpdateUserData struct { Id string `json:"id"` Email string `json:"email"` } PostAuthJson(handle, "/user/info", dbtypes.User_Normal, func(c *Context, dat *UpdateUserData) *Error { 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 := 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.E500M("Falied to get data for user", err) } } var data JustId err := GetDBOnce(c, &data, "users where email=$1", dat.Email) if err != nil && err != NotFoundError { return c.E500M("Falied to get data for user", 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.E500M("Failed to update data", err) } var user struct { Id string Username string Email string User_Type int } err = GetDBOnce(c, &user, "users where id=$1", dat.Id) if err != nil { return c.E500M("Failed to get user data", err) } toReturnUser := dbtypes.User{ Id: user.Id, Username: user.Username, Email: user.Email, UserType: user.User_Type, } return c.SendJSON(toReturnUser) }) type PasswordUpdate struct { Old_Password string `json:"old_password" validate:"required"` Password string `json:"password" validate:"required"` Password2 string `json:"password2" validate:"required"` } PostAuthJson(handle, "/user/info/password", dbtypes.User_Normal, func(c *Context, dat *PasswordUpdate) *Error { if dat.Password != dat.Password2 { return c.JsonBadRequest("New passwords did not match") } // TODO remote token _, login := generateToken(db, c.User.Email, dat.Old_Password, "Update password Token") if !login { return c.JsonBadRequest("Password is incorrect") } salt := generateSalt() hash_password, err := hashPassword(dat.Password, salt) if err != nil { return c.E500M("Failed to parse the password", 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.E500M("Failed to update password", err) } return c.SendJSON(c.User.Id) }) type TokenList struct { Id string `json:"id"` Page int `json:"page"` } PostAuthJson[TokenList](handle, "/user/token/list", 1, func(c *Context, obj *TokenList) *Error { if obj.Id == "" { obj.Id = c.User.Id } if obj.Id != c.User.Id && c.User.UserType < int(dbtypes.User_Admin) { return c.JsonBadRequest("Could not find user tokens") } type Token struct { CreationDate time.Time `json:"create_date" db:"emit_day"` TimeToLive int `json:"time_to_live" db:"time_to_live"` Name string `json:"name"` } tokens, err := GetDbMultitple[Token](c, "tokens where user_id=$1 order by emit_day desc limit 11 offset $2;", obj.Id, 10*obj.Page) if err != nil { return c.E500M("Failed get tokens", err) } max_len := min(11, len(tokens)) c.ShowMessage = false return c.SendJSON(struct { TokenList []*Token `json:"token_list"` ShowNext bool `json:"show_next"` }{ tokens[0:max_len], len(tokens) > 10, }) }) type NewToken struct { Name string `json:"name" validate:"required"` ExpiryTime int `json:"expiry"` Password string `json:"password" validate:"required"` } PostAuthJson(handle, "/user/token/add", User_Normal, func(c *Context, obj *NewToken) *Error { // TODO handle this for admin token, generated := generateToken(c.Db, c.User.Email, obj.Password, obj.Name) if !generated { return c.JsonBadRequest("Password provided is incorrect") } _, err := c.Db.Exec("update tokens set time_to_live=$1 where token=$2 and user_id=$3", obj.ExpiryTime, token, c.User.Id) if err != nil { return c.E500M("Failed to update token info", err) } return c.SendJSON(struct { Name string `json:"name"` ExpiryTime int `json:"expiry"` Token string `json:"token"` }{ obj.Name, obj.ExpiryTime, token, }) }) type TokenDelete struct { Time time.Time `json:"time" validate:"required"` } DeleteAuthJson(handle, "/user/token", User_Normal, func(c *Context, obj *TokenDelete) *Error { // TODO allow admin user to delete to other persons token err := deleteToken(c.Db, c.User.Id, obj.Time) if err != nil { return c.E500M("Could not delete token", err) } return c.SendJSON("Ok") }) type DeleteUser struct { Id string `json:"id" validate:"required"` Password string `json:"password" validate:"required"` } DeleteAuthJson(handle, "/user/delete", User_Normal, func(c *Context, obj *DeleteUser) *Error { // TODO let admin delete users if c.User.Id != obj.Id { return c.E500M("TODO implement this for admins", nil) } // TODO let admin delete users // Verify the user password _, generated := generateToken(c.Db, c.User.Email, obj.Password, "User Verification") if !generated { return c.JsonBadRequest("Password provided is incorrect") } // Disable the ability for the user to create new tasks _, err := c.Db.Exec( "update models as m set can_train=0 "+ "from users as u "+ "where u.id=$1 and u.id=m.user_id;", obj.Id, ) if err != nil { return c.E500M("Failed to stop the models", err) } type CreateNewTask struct { UserId string `db:"user_id"` TaskType int `db:"task_type"` Status int `db:"status"` ExtraTaskInfo string `db:"extra_task_info"` } newTask := CreateNewTask{ UserId: c.Handle.Config.ServiceUser.UserId, // TODO move this to an enum TaskType: int(TASK_TYPE_DELETE_USER), Status: TASK_PREPARING, ExtraTaskInfo: obj.Id, } t_id, err := InsertReturnId(c, &newTask, "tasks", "id") if err != nil { return c.E500M("Failed to create task", err) } task := Task{Id: t_id} type taskId struct { Id string `db:"t.id"` } tasks, err := GetDbMultitple[taskId](c, "tasks as t "+ "left join models as m on m.id=t.model_id "+ "where (t.user_id=$1 or m.user_id=$1) and t.status in (0,1,2,3) "+ "group by t.id;", obj.Id) for i := range tasks { err = task.Depend(c, tasks[i].Id) if err != nil { c.Logger.Error("Failed to mark task as depency", "err", err) } } err = task.UpdateStatus(c, TASK_TODO, "Task ready") if err != nil { return c.E500M("Failed to mark task as ready", err) } _, err = c.Db.Exec("update users set user_type=-1 where id=$1", obj.Id) if err != nil { return c.E500M("Failed to delete user", err) } return c.SendJSON("User to be deleted") }) }