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 }) }