Register API complete, email validation too

This commit is contained in:
Daniel Mason 2021-03-25 23:09:17 +13:00
parent 9420566cc9
commit 1018742a22
5 changed files with 113 additions and 44 deletions

View File

@ -36,7 +36,7 @@ func HandleRequests() {
v1.HandleFunc("/profile/{id}", jwtMiddleware(serveEndpoint)) v1.HandleFunc("/profile/{id}", jwtMiddleware(serveEndpoint))
// No Auth // No Auth
v1.HandleFunc("/register", serveEndpoint).Methods("POST") v1.HandleFunc("/register", handleRegister).Methods("POST")
v1.HandleFunc("/login", serveEndpoint).Methods("POST") v1.HandleFunc("/login", serveEndpoint).Methods("POST")
v1.HandleFunc("/logout", serveEndpoint).Methods("POST") v1.HandleFunc("/logout", serveEndpoint).Methods("POST")
@ -52,16 +52,30 @@ func HandleRequests() {
} }
// MIDDLEWARE // MIDDLEWARE
// throwUnauthorized - Throws a 403
func throwUnauthorized(w http.ResponseWriter, m string) {
jr := jsonResponse{
Err: m,
}
js, _ := json.Marshal(&jr)
err := errors.New(string(js))
http.Error(w, err.Error(), http.StatusUnauthorized)
}
// throwUnauthorized - Throws a 403 :
func throwBadReq(w http.ResponseWriter, m string) {
jr := jsonResponse{
Err: m,
}
js, _ := json.Marshal(&jr)
err := errors.New(string(js))
http.Error(w, err.Error(), http.StatusBadRequest)
}
// tokenMiddleware - Validates token to a user // tokenMiddleware - Validates token to a user
func tokenMiddleware(next http.HandlerFunc) http.HandlerFunc { func tokenMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(res http.ResponseWriter, req *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
jr := jsonResponse{ throwUnauthorized(w, "Invalid API Token")
Err: "Invalid API Token",
}
js, _ := json.Marshal(&jr)
err := errors.New(string(js))
http.Error(res, err.Error(), http.StatusUnauthorized)
return return
// next(res, req) // next(res, req)
} }
@ -69,31 +83,45 @@ func tokenMiddleware(next http.HandlerFunc) http.HandlerFunc {
// jwtMiddleware - Validates middleware to a user // jwtMiddleware - Validates middleware to a user
func jwtMiddleware(next http.HandlerFunc) http.HandlerFunc { func jwtMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(res http.ResponseWriter, req *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
jr := jsonResponse{ throwUnauthorized(w, "Invalid JWT Token")
Err: "Invalid JWT Token",
}
js, _ := json.Marshal(&jr)
err := errors.New(string(js))
http.Error(res, err.Error(), http.StatusUnauthorized)
return return
// next(res, req) // next(res, req)
} }
} }
// ENDPOINT HANDLING // API ENDPOINT HANDLING
// handleRegister - Does as it says!
func handleRegister(w http.ResponseWriter, r *http.Request) {
regReq := RegisterRequest{}
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&regReq)
if err != nil {
throwBadReq(w, err.Error())
return
}
err = createUser(&regReq)
if err != nil {
throwBadReq(w, err.Error())
return
}
// Lets trick 'em for now ;) ;)
fmt.Fprintf(w, "{}")
}
// serveEndpoint - API stuffs // serveEndpoint - API stuffs
func serveEndpoint(w http.ResponseWriter, r *http.Request) { func serveEndpoint(w http.ResponseWriter, r *http.Request) {
var jsonInput map[string]interface{} json, err := decodeJson(r.Body)
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&jsonInput)
if err != nil { if err != nil {
// If we can't decode. Lets tell them nicely. // If we can't decode. Lets tell them nicely.
http.Error(w, "{\"error\":\"Invalid JSON\"}", http.StatusBadRequest) http.Error(w, "{\"error\":\"Invalid JSON\"}", http.StatusBadRequest)
return return
} }
log.Printf("%v", json)
// Lets trick 'em for now ;) ;) // Lets trick 'em for now ;) ;)
fmt.Fprintf(w, "{}") fmt.Fprintf(w, "{}")
} }

View File

@ -3,6 +3,7 @@ package goscrobble
import ( import (
"errors" "errors"
"fmt" "fmt"
"time"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
@ -10,44 +11,59 @@ import (
const bCryptCost = 16 const bCryptCost = 16
type User struct { type User struct {
UUID string `json:"uuid"` UUID string `json:"uuid"`
CreatedAt time.Time `json:"created_at"`
Username string `json:"username"`
password []byte
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"` Username string `json:"username"`
password []byte
Email string `json:"email"` Email string `json:"email"`
Verified bool `json:"verified"` Password string `json:"password"`
Active bool `json:"active"`
Admin bool `json:"admin"`
} }
// createUser - Called from API // createUser - Called from API
func createUser(username string, email string, password string) error { func createUser(req *RegisterRequest) error {
// Check if user already exists.. // Check if user already exists..
if len(password) < 8 { if len(req.Password) < 8 {
return errors.New("Password must be at least 8 characters") return errors.New("Password must be at least 8 characters")
} }
// Check username is set // Check username is set
if username == "" { if req.Username == "" {
return errors.New("A username is required") return errors.New("A username is required")
} }
// 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! // Check if user or email exists!
if userAlreadyExists(username, email) { if userAlreadyExists(req) {
return errors.New("Username or email already exists") return errors.New("Username or email already exists")
} }
// Lets hashit! // Lets hashit!
hash, err := hashPassword(password) hash, err := hashPassword(req.Password)
if err != nil { if err != nil {
return err return err
} }
return insertUser(username, email, hash) return insertUser(req.Username, req.Email, hash)
} }
// insertUser - Does the dirtywork! // insertUser - Does the dirtywork!
func insertUser(username string, email string, password []byte) error { func insertUser(username string, email string, password []byte) error {
_, err := db.Exec("INSERT INTO users (uuid, username, email, password) VALUES (UUID_TO_BIN(UUID(), true),'?','?','?')", username, email, password) _, err := db.Exec("INSERT INTO users (uuid, created_at, username, email, password) VALUES (UUID_TO_BIN(UUID(), true),NOW(),?,?,?)", username, email, password)
return err return err
} }
@ -69,14 +85,16 @@ func isValidPassword(password string, user User) bool {
// userAlreadyExists - Returns bool indicating if a record exists for either username or email // userAlreadyExists - Returns bool indicating if a record exists for either username or email
// Using two look ups to make use of DB indexes. // Using two look ups to make use of DB indexes.
func userAlreadyExists(username string, email string) bool { func userAlreadyExists(req *RegisterRequest) bool {
var usernameCount, emailCount int var userExists int
err := db.QueryRow("SELECT COUNT(*) FROM users WHERE username = '?'", username).Scan(&usernameCount) err := db.QueryRow("SELECT COUNT(*) FROM users WHERE username = ?", req.Username).Scan(&userExists)
// Only run email check if there's an email... if userExists > 0 {
if email != "" { return true
err = db.QueryRow("SELECT COUNT(*) FROM users WHERE email = '?'", email).Scan(&emailCount) }
} else {
emailCount = 0 if req.Email != "" {
// Only run email check if there's an email...
err = db.QueryRow("SELECT COUNT(*) FROM users WHERE email = ?", req.Email).Scan(&userExists)
} }
if err != nil { if err != nil {
@ -84,8 +102,5 @@ func userAlreadyExists(username string, email string) bool {
return true return true
} }
count := usernameCount + emailCount return userExists > 0
// If there is more than one.. Return true. User exists.
return count != 0
} }

View File

@ -1 +1,26 @@
package goscrobble package goscrobble
import (
"encoding/json"
"io"
"regexp"
)
var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
// decodeJson - Returns a map[string]interface{}
func decodeJson(body io.ReadCloser) (map[string]interface{}, error) {
var jsonInput map[string]interface{}
decoder := json.NewDecoder(body)
err := decoder.Decode(&jsonInput)
return jsonInput, err
}
// isEmailValid - checks if the email provided passes the required structure and length.
func isEmailValid(e string) bool {
if len(e) < 3 && len(e) > 254 {
return false
}
return emailRegex.MatchString(e)
}

View File

@ -1 +1 @@
DROP TABLE IF EXISTS users; DROP TABLE IF EXISTS `users`;

View File

@ -1,5 +1,6 @@
CREATE TABLE IF NOT EXISTS `users` ( CREATE TABLE IF NOT EXISTS `users` (
`uuid` BINARY(16) PRIMARY KEY, `uuid` BINARY(16) PRIMARY KEY,
`created_at` DATETIME NOT NULL,
`username` VARCHAR(64) NOT NULL, `username` VARCHAR(64) NOT NULL,
`password` VARCHAR(60) NOT NULL, `password` VARCHAR(60) NOT NULL,
`email` VARCHAR(255) NULL DEFAULT NULL, `email` VARCHAR(255) NULL DEFAULT NULL,