feat: implement users database
This commit is contained in:
parent
16d58d8202
commit
84ba464f2d
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
users.db
|
||||||
6
go.mod
6
go.mod
@ -1,3 +1,9 @@
|
|||||||
module playback-device-server
|
module playback-device-server
|
||||||
|
|
||||||
go 1.24.1
|
go 1.24.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.24 // indirect
|
||||||
|
golang.org/x/crypto v0.36.0 // indirect
|
||||||
|
)
|
||||||
|
|||||||
6
go.sum
Normal file
6
go.sum
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
|
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||||
|
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||||
67
main/main.go
67
main/main.go
@ -1,7 +1,70 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"playback-device-server/users"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DEFAULT_USERNAME = "admin"
|
||||||
|
const MIN_PASSWORD_LENGTH = 8
|
||||||
|
const USER_DATABASE_DIR = "."
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Println("Hello, world!")
|
userDatabase := users.UserDatabase{}
|
||||||
|
userDatabase.SetDirectory(USER_DATABASE_DIR)
|
||||||
|
err := userDatabase.Initialize()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer userDatabase.Close()
|
||||||
|
|
||||||
|
exists, error := userDatabase.UsernameExists(DEFAULT_USERNAME)
|
||||||
|
if error != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
password, error := generateRandomPassword(MIN_PASSWORD_LENGTH)
|
||||||
|
if error != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Creating default admin user:")
|
||||||
|
fmt.Printf("Username: %s\n", DEFAULT_USERNAME)
|
||||||
|
fmt.Printf("Password: %s\n", password)
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
user := users.User{Username: DEFAULT_USERNAME, Password: password, IsAdmin: true}
|
||||||
|
_, error = userDatabase.CreateUser(user.Username, user.Password, user.IsAdmin)
|
||||||
|
if error != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateRandomPassword(length int) (string, error) {
|
||||||
|
numBytes := length * 3 / 4 // Base64 encoding increases length by 4/3
|
||||||
|
|
||||||
|
randomBytes := make([]byte, numBytes)
|
||||||
|
_, err := rand.Read(randomBytes)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
password := base64.URLEncoding.EncodeToString(randomBytes)
|
||||||
|
|
||||||
|
if len(password) > length {
|
||||||
|
password = password[:length]
|
||||||
|
}
|
||||||
|
|
||||||
|
return password, nil
|
||||||
}
|
}
|
||||||
|
|||||||
9
users/session.go
Normal file
9
users/session.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package users
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Session struct {
|
||||||
|
UserID string
|
||||||
|
Token string
|
||||||
|
ExpiryDate time.Time
|
||||||
|
}
|
||||||
8
users/user.go
Normal file
8
users/user.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package users
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
IsAdmin bool `json:"is_admin"`
|
||||||
|
}
|
||||||
195
users/user_database.go
Normal file
195
users/user_database.go
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
package users
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserDatabase struct {
|
||||||
|
Connection *sql.DB
|
||||||
|
databaseDirectory string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *UserDatabase) Initialize() error {
|
||||||
|
connection, error := sql.Open("sqlite3", filepath.Join(db.databaseDirectory, "users.db"))
|
||||||
|
if error != nil {
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
db.Connection = connection
|
||||||
|
|
||||||
|
_, error = db.Connection.Exec(`CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
username TEXT UNIQUE,
|
||||||
|
password TEXT,
|
||||||
|
is_admin INTEGER
|
||||||
|
)`)
|
||||||
|
if error != nil {
|
||||||
|
return fmt.Errorf("error creating users table: %s", error)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, error = db.Connection.Exec(`CREATE TABLE IF NOT EXISTS sessions (
|
||||||
|
token TEXT PRIMARY KEY,
|
||||||
|
user_id INTEGER,
|
||||||
|
expiry_date TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||||
|
)`)
|
||||||
|
if error != nil {
|
||||||
|
return fmt.Errorf("error creating sessions table: %s", error)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *UserDatabase) Close() error {
|
||||||
|
return db.Connection.Close()
|
||||||
|
}
|
||||||
|
func (db *UserDatabase) CreateUser(username, password string, isAdmin bool) (string, error) {
|
||||||
|
userID := uuid.New()
|
||||||
|
hashedPassword, err := hashPassword(password)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Connection.Exec("INSERT INTO users (id, username, password, is_admin) VALUES (?, ?, ?, ?)", userID.String(), username, hashedPassword, isAdmin)
|
||||||
|
return userID.String(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *UserDatabase) CreateSession(userID string, expiryDate time.Time) (string, error) {
|
||||||
|
sessionToken := uuid.New()
|
||||||
|
_, err := db.Connection.Exec("INSERT INTO sessions (user_id, token, expiry_date) VALUES (?, ?, ?)", userID, sessionToken.String(), expiryDate)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return sessionToken.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *UserDatabase) GetUserByUsername(username string) (*User, error) {
|
||||||
|
var user User
|
||||||
|
err := db.Connection.QueryRow("SELECT id, username, password, is_admin FROM users WHERE username = ?", username).Scan(&user.ID, &user.Username, &user.Password, &user.IsAdmin)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *UserDatabase) GetUserById(id string) (*User, error) {
|
||||||
|
var user User
|
||||||
|
err := db.Connection.QueryRow("SELECT id, username, password, is_admin FROM users WHERE id = ?", id).Scan(&user.ID, &user.Username, &user.Password, &user.IsAdmin)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *UserDatabase) UsernameExists(username string) (bool, error) {
|
||||||
|
var exists bool
|
||||||
|
err := db.Connection.QueryRow("SELECT EXISTS(SELECT 1 FROM users WHERE username = ?)", username).Scan(&exists)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return exists, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *UserDatabase) UserIdExists(id string) (bool, error) {
|
||||||
|
var exists bool
|
||||||
|
err := db.Connection.QueryRow("SELECT EXISTS(SELECT 1 FROM users WHERE id = ?)", id).Scan(&exists)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return exists, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *UserDatabase) UpdateUser(user *User) error {
|
||||||
|
_, err := db.Connection.Exec("UPDATE users SET username = ?, is_admin = ? WHERE id = ?", user.Username, user.IsAdmin, user.ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *UserDatabase) UpdatePassword(userId string, newPassword string) error {
|
||||||
|
hashedPassword, err := hashPassword(newPassword)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Connection.Exec("UPDATE users SET password = ? WHERE id = ?", hashedPassword, userId)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *UserDatabase) CheckCredentials(username, password string) (bool, error) {
|
||||||
|
var hashedPassword string
|
||||||
|
err := db.Connection.QueryRow("SELECT password FROM users WHERE username = ?", username).Scan(&hashedPassword)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
|
||||||
|
if err != nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *UserDatabase) GetSession(sessionToken string) (*Session, error) {
|
||||||
|
var session Session
|
||||||
|
row := db.Connection.QueryRow("SELECT token, user_id, expiry_date FROM sessions WHERE token = ?", sessionToken)
|
||||||
|
err := row.Scan(&session.Token, &session.UserID, &session.ExpiryDate)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &session, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *UserDatabase) DeleteSessionByToken(token string) error {
|
||||||
|
_, err := db.Connection.Exec("DELETE FROM sessions WHERE token = ?", token)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *UserDatabase) GetUsers() (*[]User, error) {
|
||||||
|
var users []User
|
||||||
|
|
||||||
|
rows, err := db.Connection.Query("SELECT id, username, is_admin FROM users")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var user User
|
||||||
|
err := rows.Scan(&user.ID, &user.Username, &user.IsAdmin)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
users = append(users, user)
|
||||||
|
}
|
||||||
|
return &users, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *UserDatabase) DeleteUser(ID string) error {
|
||||||
|
_, err := db.Connection.Exec("DELETE FROM users WHERE id = ?", ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashPassword(password string) (string, error) {
|
||||||
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(hashedPassword), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *UserDatabase) SetDirectory(directory string) {
|
||||||
|
db.databaseDirectory = directory
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user