Add user table + funcs

This commit is contained in:
Daniel Mason 2021-03-24 22:28:05 +13:00
parent c6ef84cd5a
commit 671254223a
12 changed files with 172 additions and 571 deletions

View file

@ -1,5 +0,0 @@
package goscrobble
var (
DBVersion = 1
)

View file

@ -10,15 +10,10 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/golang-migrate/migrate"
"github.com/golang-migrate/migrate/database/mysql"
_ "github.com/golang-migrate/migrate/v4/source/file"
_ "github.com/golang-migrate/migrate/source/file"
)
type dbItem struct {
conn *sql.DB
version *int
}
var db dbItem
var db *sql.DB
// InitDb - Boots up a DB connection
func InitDb() {
@ -27,7 +22,7 @@ func InitDb() {
dbPass := os.Getenv("MYSQL_PASS")
dbName := os.Getenv("MYSQL_DB")
dbConn, err := sql.Open("mysql", dbUser+":"+dbPass+"@tcp("+dbHost+")/"+dbName)
dbConn, err := sql.Open("mysql", dbUser+":"+dbPass+"@tcp("+dbHost+")/"+dbName+"?multiStatements=true")
if err != nil {
panic(err)
}
@ -41,73 +36,38 @@ func InitDb() {
panic(err)
}
var vers int = 0
db = dbItem{
conn: dbConn,
version: &vers,
}
if !checkIfDbUpToDate() {
fmt.Printf("Database not up to date.. triggering migrations!\n")
} else {
fmt.Printf("Database up to date!\n")
}
db = dbConn
runMigrations()
}
// CloseDbConn - Closes DB connection
func CloseDbConn() {
db.conn.Close()
db.Close()
}
// checkIfDbUpToDate - Checks if we need to run migrations
func checkIfDbUpToDate() bool {
fmt.Printf("Code version: %v. ", DBVersion)
dbVers := db.getDbVersion()
fmt.Printf("DB version: %v.\n", dbVers)
if dbVers == DBVersion {
return true
} else if dbVers > DBVersion {
panic("!!Warning!! Your database is newer than the code. Please update!")
}
return false
}
// getDbVersion - Gets version of schema or generate basic schema
func (db dbItem) getDbVersion() int {
stmtOut, err := db.conn.Prepare("SELECT version FROM goscrobble WHERE id = ? ")
defer stmtOut.Close()
if err != nil {
// We can assume this is a fresh database - Lets config it!
return runMigrations(DBVersion)
}
err = stmtOut.QueryRow(1).Scan(db.version)
if err != nil {
panic(err.Error())
}
return *db.version
}
func runMigrations(latestVersion int) int {
driver, err := mysql.WithInstance(db.conn, &mysql.Config{})
func runMigrations() {
driver, err := mysql.WithInstance(db, &mysql.Config{})
if err != nil {
log.Fatalf("Unable to run migrations! %v", err)
}
m, _ := migrate.NewWithDatabaseInstance(
"file:///migrations",
m, err := migrate.NewWithDatabaseInstance(
"file://migrations",
"mysql",
driver,
)
if err != nil {
panic(fmt.Errorf("Error fetching DB Migrations %v", err))
}
m.Steps(2)
err = m.Up()
if err != nil {
// Skip 'no change'. This is fine. Everything is fine.
if err.Error() == "no change" {
return
}
return latestVersion
panic(fmt.Errorf("Error running DB Migrations %v", err))
}
}

View file

@ -10,51 +10,46 @@ import (
"github.com/gorilla/mux"
)
// spaHandler - Handles Single Page Applications (React)
type spaHandler struct {
staticPath string
indexPath string
}
// HandleRequests - Boot HTTP server
// HandleRequests - Boot HTTP!
func HandleRequests() {
// Creates a new router
// Create a new router
httpRouter := mux.NewRouter().StrictSlash(true)
httpRouter.HandleFunc("/api/v1", serveEndpoint)
httpRouter.HandleFunc("/api/v1/scrobble/jellyfin", serveEndpoint)
// STATIC TOKEN AUTH
httpRouter.HandleFunc("/api/v1/ingress/jellyfin", serveEndpoint)
// JWT AUTH?
httpRouter.HandleFunc("/api/v1/profile/{id}", serveEndpoint)
// NO AUTH
httpRouter.HandleFunc("/api/v1/login", serveEndpoint)
httpRouter.HandleFunc("/api/v1/logout", serveEndpoint)
httpRouter.HandleFunc("/api/v1/register", serveEndpoint)
// SERVE FRONTEND - NO AUTH
spa := spaHandler{staticPath: "web/build", indexPath: "index.html"}
httpRouter.PathPrefix("/").Handler(spa)
// fileServer := http.FileServer(http.Dir("web"))
// fileMatcher := regexp.MustCompile(`\.[a-zA-Z]*$`)
// http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// if !fileMatcher.MatchString(r.URL.Path) {
// http.ServeFile(w, r, "web/build/index.html")
// } else {
// fileServer.ServeHTTP(w, r)
// }
// })
// Serve HTTP Server
// Serve it up!
log.Fatal(http.ListenAndServe(":42069", httpRouter))
}
// serveFrontend - Handle / queries
func serveFrontend(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Welcome!")
}
func serveEndpoint(w http.ResponseWriter, r *http.Request) {
// Lets trick 'em for now
fmt.Fprintf(w, "{}")
}
func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// get the absolute path to prevent directory traversal
// Get the absolute path to prevent directory traversal
path, err := filepath.Abs(r.URL.Path)
if err != nil {
// if we failed to get the absolute path respond with a 400 bad request
// and stop
// If we failed to get the absolute path respond with a 400 bad request and return
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

View file

@ -1 +1,91 @@
package goscrobble
import (
"errors"
"fmt"
"golang.org/x/crypto/bcrypt"
)
const bCryptCost = 16
type User struct {
UUID string `json:"uuid"`
Username string `json:"username"`
password []byte
Email string `json:"email"`
Verified bool `json:"verified"`
Active bool `json:"active"`
Admin bool `json:"admin"`
}
// createUser - Called from API
func createUser(username string, email string, password string) error {
// Check if user already exists..
if len(password) < 8 {
return errors.New("Password must be at least 8 characters")
}
// Check username is set
if username == "" {
return errors.New("A username is required")
}
// Check if user or email exists!
if userAlreadyExists(username, email) {
return errors.New("Username or email already exists")
}
// Lets hashit!
hash, err := hashPassword(password)
if err != nil {
return err
}
return insertUser(username, 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)
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(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
}
if err != nil {
fmt.Printf("Error querying for duplicate users: %v", err)
return true
}
count := usernameCount + emailCount
// If there is more than one.. Return true. User exists.
return count != 0
}