426 lines
11 KiB
Go
426 lines
11 KiB
Go
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"
|
|
)
|
|
|
|
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(w, 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(w, 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(w, 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(w, http.StatusBadRequest, "Email already in use!")
|
|
}
|
|
if (db_username == dat.Username) {
|
|
return c.SendJSONStatus(w, http.StatusBadRequest, "Username already in use!")
|
|
}
|
|
panic("Unrechable")
|
|
}
|
|
|
|
if len([]byte(dat.Password)) > 68 {
|
|
return c.JsonBadRequest(w, "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(w, 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(w, 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
|
|
})
|
|
|
|
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 {
|
|
return c.Error500(nil)
|
|
}
|
|
|
|
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
|
|
})
|
|
}
|