feat: added users db, login, register, logout
This commit is contained in:
246
users.go
Normal file
246
users.go
Normal file
@@ -0,0 +1,246 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
id string
|
||||
username string
|
||||
email string
|
||||
user_type int
|
||||
}
|
||||
|
||||
var ErrUserNotFound = errors.New("User Not found")
|
||||
|
||||
func userFromToken(db *sql.DB, token string) (*User, error) {
|
||||
row, err := db.Query("select users.id, users.username, users.email, users.user_type from users inner join tokens on tokens.user_id = users.id where tokens.token = $1;", token)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var id string
|
||||
var username string
|
||||
var email string
|
||||
var user_type int
|
||||
|
||||
if !row.Next() {
|
||||
return nil, ErrUserNotFound
|
||||
}
|
||||
|
||||
err = row.Scan(&id, &username, &email, &user_type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &User{id, username, email, user_type}, nil
|
||||
}
|
||||
|
||||
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))
|
||||
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))
|
||||
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
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user