fyp/logic/users/users.go

516 lines
14 KiB
Go

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)
})
PostAuthJson(handle, "/user/info/get", User_Admin, func(c *Context, dat *JustId) *Error {
var user *User
user, err := UserFromId(c, dat.Id)
if err == NotFoundError {
return c.SendJSONStatus(404, "User not found")
} else if err != nil {
return c.E500M("Could not get user", err)
}
return c.SendJSON(user)
})
// 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")
})
handle.DeleteAuth("/user/token/logoff", User_Normal, func(c *Context) *Error {
if c.Token == nil {
return c.JsonBadRequest("Failed to get token")
}
_, err := c.Db.Exec("delete from tokens where token=$1;", c.Token)
if err != nil {
return c.E500M("Failed to 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")
})
}