2023-09-19 13:39:59 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/rand"
|
|
|
|
"database/sql"
|
|
|
|
"encoding/hex"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
2024-04-13 16:59:08 +01:00
|
|
|
"time"
|
2023-09-19 13:39:59 +01:00
|
|
|
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
|
|
|
2024-04-14 14:51:16 +01:00
|
|
|
. "git.andr3h3nriqu3s.com/andr3/fyp/logic/db_types"
|
2024-02-24 11:34:31 +00:00
|
|
|
dbtypes "git.andr3h3nriqu3s.com/andr3/fyp/logic/db_types"
|
2023-10-25 14:22:45 +01:00
|
|
|
. "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils"
|
2023-09-21 16:43:11 +01:00
|
|
|
)
|
2023-09-19 13:39:59 +01:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2024-04-13 16:59:08 +01:00
|
|
|
func deleteToken(db *sql.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 *sql.DB, email string, password string, name string) (string, bool) {
|
2023-09-19 13:39:59 +01:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
2024-02-24 11:34:31 +00:00
|
|
|
if err = bcrypt.CompareHashAndPassword([]byte(db_password), append([]byte(password), bytes_salt...)); err != nil {
|
2023-09-19 13:39:59 +01:00
|
|
|
return "", false
|
|
|
|
}
|
|
|
|
|
|
|
|
token := genToken()
|
|
|
|
|
2024-04-13 16:59:08 +01:00
|
|
|
_, err = db.Exec("insert into tokens (user_id, token, name) values ($1, $2, $3);", db_id, token, name)
|
2023-09-19 13:39:59 +01:00
|
|
|
if err != nil {
|
|
|
|
return "", false
|
|
|
|
}
|
|
|
|
|
|
|
|
return token, true
|
|
|
|
}
|
|
|
|
|
|
|
|
func usersEndpints(db *sql.DB, handle *Handle) {
|
|
|
|
|
2024-04-13 23:55:01 +01:00
|
|
|
type UserLogin struct {
|
|
|
|
Email string `json:"email"`
|
|
|
|
Password string `json:"password"`
|
|
|
|
}
|
|
|
|
PostAuthJson(handle, "/login", dbtypes.User_Not_Auth, func(c *Context, dat *UserLogin) *Error {
|
2023-09-19 13:39:59 +01:00
|
|
|
// TODO Give this to the generateToken function
|
2024-04-13 16:59:08 +01:00
|
|
|
token, login := generateToken(db, dat.Email, dat.Password, "Logged in user")
|
2023-09-19 13:39:59 +01:00
|
|
|
if !login {
|
2024-03-09 10:52:08 +00:00
|
|
|
return c.SendJSONStatus(http.StatusUnauthorized, "Email or password are incorrect")
|
2023-09-19 13:39:59 +01:00
|
|
|
}
|
|
|
|
|
2024-03-09 10:52:08 +00:00
|
|
|
user, err := dbtypes.UserFromToken(c.Db, token)
|
|
|
|
if err != nil {
|
2024-04-13 23:55:01 +01:00
|
|
|
return c.E500M("Failed to get user from token", err)
|
2024-03-09 10:52:08 +00:00
|
|
|
}
|
2024-02-24 11:34:31 +00:00
|
|
|
|
2024-03-09 10:52:08 +00:00
|
|
|
type UserReturn struct {
|
|
|
|
Token string `json:"token"`
|
|
|
|
Id string `json:"id"`
|
|
|
|
UserType int `json:"user_type"`
|
|
|
|
Username string `json:"username"`
|
|
|
|
Email string `json:"email"`
|
|
|
|
}
|
2024-02-24 11:34:31 +00:00
|
|
|
|
2024-03-09 10:52:08 +00:00
|
|
|
userReturn := UserReturn{
|
|
|
|
Token: token,
|
|
|
|
Id: user.Id,
|
|
|
|
UserType: user.UserType,
|
|
|
|
Username: user.Username,
|
|
|
|
Email: user.Email,
|
|
|
|
}
|
2024-02-24 11:34:31 +00:00
|
|
|
|
2024-03-09 10:52:08 +00:00
|
|
|
return c.SendJSON(userReturn)
|
|
|
|
})
|
2024-02-24 11:34:31 +00:00
|
|
|
|
2024-04-13 23:55:01 +01:00
|
|
|
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 {
|
2024-03-09 10:52:08 +00:00
|
|
|
return c.SendJSONStatus(http.StatusBadRequest, "Email already in use!")
|
|
|
|
}
|
2024-04-13 23:55:01 +01:00
|
|
|
if prevUser.Username == dat.Username {
|
2024-03-09 10:52:08 +00:00
|
|
|
return c.SendJSONStatus(http.StatusBadRequest, "Username already in use!")
|
|
|
|
}
|
2023-09-19 13:39:59 +01:00
|
|
|
}
|
|
|
|
|
2024-03-09 10:52:08 +00:00
|
|
|
if len([]byte(dat.Password)) > 68 {
|
|
|
|
return c.JsonBadRequest("Password is to long!")
|
2023-09-19 13:39:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
salt := generateSalt()
|
2024-03-09 10:52:08 +00:00
|
|
|
hash_password, err := hashPassword(dat.Password, salt)
|
2023-09-19 13:39:59 +01:00
|
|
|
if err != nil {
|
2024-04-13 23:55:01 +01:00
|
|
|
return c.E500M("Falied to store password", err)
|
2023-09-19 13:39:59 +01:00
|
|
|
}
|
|
|
|
|
2024-03-09 10:52:08 +00:00
|
|
|
_, err = db.Exec("insert into users (username, email, salt, password) values ($1, $2, $3, $4);", dat.Username, dat.Email, salt, hash_password)
|
2023-09-19 13:39:59 +01:00
|
|
|
if err != nil {
|
2024-04-13 23:55:01 +01:00
|
|
|
return c.E500M("Falied to create user", err)
|
2023-09-19 13:39:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO Give this to the generateToken function
|
2024-04-13 16:59:08 +01:00
|
|
|
token, login := generateToken(db, dat.Email, dat.Password, "User Login")
|
2023-09-19 13:39:59 +01:00
|
|
|
if !login {
|
2024-03-09 10:52:08 +00:00
|
|
|
return c.SendJSONStatus(500, "Could not login after creatting account please try again later")
|
|
|
|
}
|
|
|
|
|
|
|
|
user, err := dbtypes.UserFromToken(c.Db, token)
|
|
|
|
if err != nil {
|
2024-04-13 23:55:01 +01:00
|
|
|
return c.E500M("Falied to create user", err)
|
2024-03-09 10:52:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type UserReturn struct {
|
|
|
|
Token string `json:"token"`
|
|
|
|
Id string `json:"id"`
|
|
|
|
UserType int `json:"user_type"`
|
|
|
|
Username string `json:"username"`
|
|
|
|
Email string `json:"email"`
|
2023-09-19 13:39:59 +01:00
|
|
|
}
|
|
|
|
|
2024-03-09 10:52:08 +00:00
|
|
|
userReturn := UserReturn{
|
|
|
|
Token: token,
|
|
|
|
Id: user.Id,
|
|
|
|
UserType: user.UserType,
|
|
|
|
Username: user.Username,
|
|
|
|
Email: user.Email,
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.SendJSON(userReturn)
|
2023-09-19 13:39:59 +01:00
|
|
|
})
|
|
|
|
|
2024-03-09 10:52:08 +00:00
|
|
|
// TODO allow admin users to update this data
|
2024-04-14 15:19:32 +01:00
|
|
|
handle.GetAuth("/user/info", dbtypes.User_Normal, func(c *Context) *Error {
|
2024-03-09 10:52:08 +00:00
|
|
|
user, err := dbtypes.UserFromToken(c.Db, *c.Token)
|
|
|
|
if err != nil {
|
2024-04-13 23:55:01 +01:00
|
|
|
return c.E500M("Falied to get user data", err)
|
2024-03-09 10:52:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
2023-10-25 14:22:45 +01:00
|
|
|
}
|
|
|
|
|
2024-03-09 10:52:08 +00:00
|
|
|
return c.SendJSON(userReturn)
|
2023-10-25 14:22:45 +01:00
|
|
|
})
|
|
|
|
|
2024-02-24 15:28:23 +00:00
|
|
|
// Handles updating users
|
2024-04-13 23:55:01 +01:00
|
|
|
type UpdateUserData struct {
|
|
|
|
Id string `json:"id"`
|
|
|
|
Email string `json:"email"`
|
|
|
|
}
|
|
|
|
PostAuthJson(handle, "/user/info", dbtypes.User_Normal, func(c *Context, dat *UpdateUserData) *Error {
|
2024-02-24 15:28:23 +00:00
|
|
|
if dat.Id != c.User.Id && c.User.UserType != int(dbtypes.User_Admin) {
|
2024-03-01 23:03:25 +00:00
|
|
|
return c.SendJSONStatus(403, "You need to be an admin to update another users account")
|
2024-02-24 15:28:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if dat.Id != c.User.Id {
|
|
|
|
var data struct {
|
|
|
|
Id string
|
|
|
|
}
|
|
|
|
|
2024-04-14 14:51:16 +01:00
|
|
|
err := GetDBOnce(c, &data, "users where id=$1", dat.Id)
|
2024-02-24 15:28:23 +00:00
|
|
|
if err == NotFoundError {
|
2024-03-01 23:03:25 +00:00
|
|
|
return c.JsonBadRequest("User does not exist")
|
2024-02-24 15:28:23 +00:00
|
|
|
} else if err != nil {
|
2024-04-13 23:55:01 +01:00
|
|
|
return c.E500M("Falied to get data for user", err)
|
2024-02-24 15:28:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-13 23:55:01 +01:00
|
|
|
var data JustId
|
2024-04-14 14:51:16 +01:00
|
|
|
err := GetDBOnce(c, &data, "users where email=$1", dat.Email)
|
2024-02-24 15:28:23 +00:00
|
|
|
if err != nil && err != NotFoundError {
|
2024-04-13 23:55:01 +01:00
|
|
|
return c.E500M("Falied to get data for user", err)
|
2024-02-24 15:28:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err != NotFoundError {
|
|
|
|
if data.Id == dat.Id {
|
2024-03-01 23:03:25 +00:00
|
|
|
return c.JsonBadRequest("Email is the name as the previous one!")
|
2024-02-24 15:28:23 +00:00
|
|
|
} else {
|
2024-03-01 23:03:25 +00:00
|
|
|
return c.JsonBadRequest("Email already in use")
|
2024-02-24 15:28:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = c.Db.Exec("update users set email=$2 where id=$1", dat.Id, dat.Email)
|
|
|
|
if err != nil {
|
2024-04-13 23:55:01 +01:00
|
|
|
return c.E500M("Failed to update data", err)
|
2024-02-24 15:28:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var user struct {
|
|
|
|
Id string
|
|
|
|
Username string
|
|
|
|
Email string
|
|
|
|
User_Type int
|
|
|
|
}
|
|
|
|
|
2024-04-14 14:51:16 +01:00
|
|
|
err = GetDBOnce(c, &user, "users where id=$1", dat.Id)
|
2024-02-24 15:28:23 +00:00
|
|
|
if err != nil {
|
2024-04-13 23:55:01 +01:00
|
|
|
return c.E500M("Failed to get user data", err)
|
2024-02-24 15:28:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
toReturnUser := dbtypes.User{
|
|
|
|
Id: user.Id,
|
|
|
|
Username: user.Username,
|
|
|
|
Email: user.Email,
|
|
|
|
UserType: user.User_Type,
|
|
|
|
}
|
|
|
|
|
2024-03-01 23:03:25 +00:00
|
|
|
return c.SendJSON(toReturnUser)
|
2024-02-24 15:28:23 +00:00
|
|
|
})
|
|
|
|
|
2024-04-13 23:55:01 +01:00
|
|
|
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 {
|
2024-03-09 10:52:08 +00:00
|
|
|
if dat.Password != dat.Password2 {
|
|
|
|
return c.JsonBadRequest("New passwords did not match")
|
2023-10-25 14:22:45 +01:00
|
|
|
}
|
|
|
|
|
2024-04-13 16:59:08 +01:00
|
|
|
// TODO remote token
|
|
|
|
_, login := generateToken(db, c.User.Email, dat.Old_Password, "Update password Token")
|
2023-10-25 14:22:45 +01:00
|
|
|
if !login {
|
2024-03-09 10:52:08 +00:00
|
|
|
return c.JsonBadRequest("Password is incorrect")
|
2023-10-25 14:22:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
salt := generateSalt()
|
2024-03-09 10:52:08 +00:00
|
|
|
hash_password, err := hashPassword(dat.Password, salt)
|
2023-10-25 14:22:45 +01:00
|
|
|
if err != nil {
|
2024-04-13 23:55:01 +01:00
|
|
|
return c.E500M("Failed to parse the password", err)
|
2023-10-25 14:22:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
_, err = db.Exec("update users set salt=$1, password=$2 where id=$3", salt, hash_password, c.User.Id)
|
|
|
|
if err != nil {
|
2024-04-13 23:55:01 +01:00
|
|
|
return c.E500M("Failed to update password", err)
|
2023-10-25 14:22:45 +01:00
|
|
|
}
|
|
|
|
|
2024-03-09 10:52:08 +00:00
|
|
|
return c.SendJSON(c.User.Id)
|
2023-10-25 14:22:45 +01:00
|
|
|
})
|
|
|
|
|
2024-04-13 16:59:08 +01:00
|
|
|
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,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2024-04-14 20:55:37 +01:00
|
|
|
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,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2024-04-13 16:59:08 +01:00
|
|
|
type TokenDelete struct {
|
|
|
|
Time time.Time `json:"time" validate:"required"`
|
|
|
|
}
|
2024-04-14 20:55:37 +01:00
|
|
|
DeleteAuthJson(handle, "/user/token", User_Normal, func(c *Context, obj *TokenDelete) *Error {
|
2024-04-13 16:59:08 +01:00
|
|
|
// 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")
|
|
|
|
})
|
2023-09-19 13:39:59 +01:00
|
|
|
}
|