GoScrobble/internal/goscrobble/user.go

201 lines
5.1 KiB
Go
Raw Normal View History

2021-03-23 08:43:44 +00:00
package goscrobble
2021-03-24 09:28:05 +00:00
import (
2021-03-25 23:21:28 +00:00
"database/sql"
"encoding/json"
2021-03-24 09:28:05 +00:00
"errors"
"fmt"
2021-03-25 23:21:28 +00:00
"log"
"net"
"strings"
"time"
2021-03-24 09:28:05 +00:00
"golang.org/x/crypto/bcrypt"
)
const bCryptCost = 16
type User struct {
2021-03-25 23:21:28 +00:00
UUID string `json:"uuid"`
CreatedAt time.Time `json:"created_at"`
CreatedIp net.IP `json:"created_ip"`
ModifiedAt time.Time `json:"modified_at"`
ModifiedIP net.IP `jsos:"modified_ip"`
Username string `json:"username"`
Password []byte `json:"password"`
Email string `json:"email"`
Verified bool `json:"verified"`
Active bool `json:"active"`
Admin bool `json:"admin"`
}
// RegisterRequest - Incoming JSON
type RegisterRequest struct {
2021-03-24 09:28:05 +00:00
Username string `json:"username"`
Email string `json:"email"`
Password string `json:"password"`
2021-03-24 09:28:05 +00:00
}
2021-03-25 23:21:28 +00:00
// RegisterRequest - Incoming JSON
type LoginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
}
// LoginResponse - JWT issued
type LoginResponse struct {
Token string `json:"token"`
}
2021-03-24 09:28:05 +00:00
// createUser - Called from API
2021-03-25 23:21:28 +00:00
func createUser(req *RegisterRequest, ip net.IP) error {
2021-03-24 09:28:05 +00:00
// Check if user already exists..
if len(req.Password) < 8 {
2021-03-24 09:28:05 +00:00
return errors.New("Password must be at least 8 characters")
}
2021-03-25 23:21:28 +00:00
// Check Username is set
if req.Username == "" {
2021-03-24 09:28:05 +00:00
return errors.New("A username is required")
}
2021-03-25 23:21:28 +00:00
// Check max length for Username
if len(req.Username) > 64 {
return errors.New("Username cannot be longer than 64 characters")
}
// Check username doesn't contain @
if strings.Contains(req.Username, "@") {
return errors.New("Username contains invalid characters")
}
// If set an email.. validate it!
if req.Email != "" {
if !isEmailValid(req.Email) {
return errors.New("Invalid email address")
}
}
2021-03-24 09:28:05 +00:00
// Check if user or email exists!
if userAlreadyExists(req) {
2021-03-24 09:28:05 +00:00
return errors.New("Username or email already exists")
}
// Lets hashit!
hash, err := hashPassword(req.Password)
2021-03-24 09:28:05 +00:00
if err != nil {
return err
}
2021-03-25 23:21:28 +00:00
return insertUser(req.Username, req.Email, hash, ip)
}
func loginUser(logReq *LoginRequest, ip net.IP) ([]byte, error) {
var resp []byte
var user User
if logReq.Username == "" {
return resp, errors.New("A username is required")
2021-03-25 23:21:28 +00:00
}
if logReq.Password == "" {
return resp, errors.New("A password is required")
2021-03-25 23:21:28 +00:00
}
if strings.Contains(logReq.Username, "@") {
err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), `username`, `email`, `password` FROM `users` WHERE `email` = ? AND `active` = 1",
logReq.Username).Scan(&user.UUID, &user.Username, &user.Email, &user.Password)
2021-03-25 23:21:28 +00:00
if err != nil {
if err == sql.ErrNoRows {
return resp, errors.New("Invalid Username or Password")
}
}
} else {
err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), `username`, `email`, `password` FROM `users` WHERE `username` = ? AND `active` = 1",
logReq.Username).Scan(&user.UUID, &user.Username, &user.Email, &user.Password)
2021-03-25 23:21:28 +00:00
if err == sql.ErrNoRows {
return resp, errors.New("Invalid Username or Password")
}
}
if !isValidPassword(logReq.Password, user) {
return resp, errors.New("Invalid Username or Password")
}
// Issue JWT + Response
token, err := generateJWTToken(user)
2021-03-25 23:21:28 +00:00
if err != nil {
log.Printf("Error generating JWT: %v", err)
return resp, errors.New("Error logging in")
}
loginResp := LoginResponse{
Token: token,
}
resp, _ = json.Marshal(&loginResp)
return resp, nil
}
2021-03-24 09:28:05 +00:00
// insertUser - Does the dirtywork!
2021-03-25 23:21:28 +00:00
func insertUser(username string, email string, password []byte, ip net.IP) error {
2021-03-28 08:52:34 +00:00
token := generateToken(32)
_, err := db.Exec("INSERT INTO users (uuid, created_at, created_ip, modified_at, modified_ip, username, email, password, token) "+
"VALUES (UUID_TO_BIN(UUID(), true),NOW(),?,NOW(),?,?,?,?,?)", ip, ip, username, email, password, token)
2021-03-25 23:21:28 +00:00
return err
}
2021-03-28 08:52:34 +00:00
func updateUser(uuid string, field string, value string, ip net.IP) error {
2021-03-25 23:21:28 +00:00
_, err := db.Exec("UPDATE users SET ? = ?, modified_at = NOW(), modified_ip = ? WHERE uuid = ?", field, value, uuid, ip)
return err
}
func updateUserDirect(uuid string, field string, value string) error {
_, err := db.Exec("UPDATE users SET ? = ? WHERE uuid = ?", field, value, uuid)
2021-03-24 09:28:05 +00:00
return err
}
// hashPassword - Returns bcrypt hash
func hashPassword(password string) ([]byte, error) {
return bcrypt.GenerateFromPassword([]byte(password), bCryptCost)
}
// isValidPassword - Checks if password is valid
func isValidPassword(password string, user User) bool {
2021-03-25 23:21:28 +00:00
err := bcrypt.CompareHashAndPassword(user.Password, []byte(password))
2021-03-24 09:28:05 +00:00
if err != nil {
return false
}
return true
}
// userAlreadyExists - Returns bool indicating if a record exists for either username or email
// Using two look ups to make use of DB indexes.
func userAlreadyExists(req *RegisterRequest) bool {
2021-03-25 23:21:28 +00:00
count, err := getDbCount("SELECT COUNT(*) FROM users WHERE username = ?", req.Username)
if err != nil {
fmt.Printf("Error querying for duplicate users: %v", err)
return true
}
if count > 0 {
return true
}
if req.Email != "" {
// Only run email check if there's an email...
2021-03-25 23:21:28 +00:00
count, err = getDbCount("SELECT COUNT(*) FROM users WHERE email = ?", req.Email)
2021-03-24 09:28:05 +00:00
}
if err != nil {
fmt.Printf("Error querying for duplicate users: %v", err)
return true
}
2021-03-25 23:21:28 +00:00
return count > 0
2021-03-24 09:28:05 +00:00
}