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

View File

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

View File

@ -1 +1,26 @@
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` (
`uuid` BINARY(16) PRIMARY KEY,
`created_at` DATETIME NOT NULL,
`username` VARCHAR(64) NOT NULL,
`password` VARCHAR(60) NOT NULL,
`email` VARCHAR(255) NULL DEFAULT NULL,