fyp/users.go

215 lines
5.1 KiB
Go

package main
import (
"crypto/rand"
"database/sql"
"encoding/hex"
"fmt"
"io"
"net/http"
"time"
"golang.org/x/crypto/bcrypt"
. "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 bcrypt.CompareHashAndPassword([]byte(db_password), append([]byte(password), bytes_salt...)) != 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 {
fmt.Println("Handle JSON")
return &Error{Code: 404}
}
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 {
return &Error{Code: http.StatusNotFound}
}
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("/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
})
}