mirror of
https://github.com/idanoo/GoScrobble.git
synced 2024-11-22 08:25:14 +00:00
214 lines
5.4 KiB
Go
214 lines
5.4 KiB
Go
package goscrobble
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/dgrijalva/jwt-go"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
const bCryptCost = 16
|
|
|
|
type User struct {
|
|
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 {
|
|
Username string `json:"username"`
|
|
Email string `json:"email"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
// RegisterRequest - Incoming JSON
|
|
type LoginRequest struct {
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
// LoginResponse - JWT issued
|
|
type LoginResponse struct {
|
|
Token string `json:"token"`
|
|
}
|
|
|
|
// createUser - Called from API
|
|
func createUser(req *RegisterRequest, ip net.IP) error {
|
|
// Check if user already exists..
|
|
if len(req.Password) < 8 {
|
|
return errors.New("Password must be at least 8 characters")
|
|
}
|
|
|
|
// Check Username is set
|
|
if req.Username == "" {
|
|
return errors.New("A username is required")
|
|
}
|
|
|
|
// 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")
|
|
}
|
|
}
|
|
|
|
// Check if user or email exists!
|
|
if userAlreadyExists(req) {
|
|
return errors.New("Username or email already exists")
|
|
}
|
|
|
|
// Lets hashit!
|
|
hash, err := hashPassword(req.Password)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
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("username must be set")
|
|
}
|
|
|
|
if logReq.Password == "" {
|
|
return resp, errors.New("password must be set")
|
|
}
|
|
|
|
if strings.Contains(logReq.Username, "@") {
|
|
err := db.QueryRow("SELECT BIN_TO_UUID(uuid), username, email, password FROM users WHERE email = ? AND active = 1", logReq.Username).Scan(&user.UUID, &user.Username, &user.Email, &user.Password)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return resp, errors.New("Invalid Username or Password")
|
|
}
|
|
}
|
|
} else {
|
|
err := db.QueryRow("SELECT BIN_TO_UUID(uuid), username, email, password FROM users WHERE username = ? AND active = 1", logReq.Username).Scan(&user.UUID, &user.Username, &user.Email, &user.Password)
|
|
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 := generateJwt(user)
|
|
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
|
|
}
|
|
|
|
func generateJwt(user User) (string, error) {
|
|
atClaims := jwt.MapClaims{}
|
|
atClaims["sub"] = user.UUID
|
|
atClaims["username"] = user.Username
|
|
atClaims["email"] = user.Email
|
|
atClaims["exp"] = time.Now().Add(JwtExpiry).Unix()
|
|
at := jwt.NewWithClaims(jwt.SigningMethodHS512, atClaims)
|
|
token, err := at.SignedString(JwtToken)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return token, nil
|
|
}
|
|
|
|
// insertUser - Does the dirtywork!
|
|
func insertUser(username string, email string, password []byte, ip net.IP) error {
|
|
_, err := db.Exec("INSERT INTO users (uuid, created_at, created_ip, modified_at, modified_ip, username, email, password) "+
|
|
"VALUES (UUID_TO_BIN(UUID(), true),NOW(),?,NOW(),?,?,?,?)", ip, ip, username, email, password)
|
|
|
|
return err
|
|
}
|
|
|
|
func updateUser(uuid string, field string, value string, ip string) error {
|
|
_, 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)
|
|
|
|
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 {
|
|
err := bcrypt.CompareHashAndPassword(user.Password, []byte(password))
|
|
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 {
|
|
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...
|
|
count, err = getDbCount("SELECT COUNT(*) FROM users WHERE email = ?", req.Email)
|
|
}
|
|
|
|
if err != nil {
|
|
fmt.Printf("Error querying for duplicate users: %v", err)
|
|
return true
|
|
}
|
|
|
|
return count > 0
|
|
}
|