rushlink/internal/db/user.go

154 lines
3.6 KiB
Go

package db
import (
"bytes"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"time"
"github.com/google/uuid"
"golang.org/x/crypto/argon2"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
User string `gorm:"uniqueIndex"`
Password string
Admin bool
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt
}
func NewUser(db *gorm.DB, username string, password string, admin bool) error {
// Generate a new UUID for the user
id, err := uuid.NewRandom()
if err != nil {
return err
}
// Hash the password using argon2id
hashedPassword, err := HashPassword(password)
if err != nil {
return err
}
// Create a new user record
user := &User{
ID: uint(id.ID()),
User: username,
Password: hashedPassword,
Admin: admin,
}
return db.Create(user).Error
}
func Authenticate(db *gorm.DB, username string, password string) (*User, error) {
// Get the user record by username
var user User
if err := db.Where("user = ?", username).First(&user).Error; err != nil {
return nil, errors.New("user not found")
}
// Compare the hashed password with the provided password
if !comparePassword(user.Password, password) {
return nil, errors.New("invalid password")
}
return &user, nil
}
func HashPassword(password string) (string, error) {
// Generate a salt for the password hash
salt := make([]byte, 16)
if _, err := rand.Read(salt); err != nil {
return "", err
}
// Hash the password using argon2id
hash := argon2.IDKey([]byte(password), salt, 2, 64*1024, 1, 64)
// Encode the salt and hash as a string
return string(salt) + string(hash), nil
}
func comparePassword(hashedPassword string, password string) bool {
// Decode the salt and hash from the hashed password string
salt := []byte(hashedPassword)[:16]
hash := []byte(hashedPassword)[16:]
// Hash the password using the same salt and parameters
computedHash := argon2.IDKey([]byte(password), salt, 2, 64*1024, 1, 64)
// Compare the computed hash with the stored hash
return bytes.Equal(hash, computedHash)
}
// DeleteUser deletes a user with the specified username from the database.
func DeleteUser(db *gorm.DB, username string) error {
// Find the user by username
var user User
if err := db.Where("user = ?", username).First(&user).Error; err != nil {
return err
}
// Delete the user
if err := db.Delete(&user).Error; err != nil {
return err
}
return nil
}
func ChangeUser(db *gorm.DB, username string, updatedUser *User) error {
// Retrieve the existing user
var existingUser User
if err := db.Where("user = ?", username).First(&existingUser).Error; err != nil {
return err
}
// Update the user fields
existingUser.User = updatedUser.User
existingUser.Password = updatedUser.Password
existingUser.Admin = updatedUser.Admin
// Save the updated user to the database
if err := db.Save(&existingUser).Error; err != nil {
return err
}
return nil
}
func CreateAdminUser(db *gorm.DB, adminUsername string) error {
// Check if the admin user already exists
var admins []User
if err := db.Unscoped().Limit(1).Where("user = ?", adminUsername).Find(&admins).Error; err != nil {
return err
}
if len(admins) > 0 {
// already exists
return nil
}
// Generate a random 24-char password
passwordBytes := make([]byte, 24)
if _, err := rand.Read(passwordBytes); err != nil {
return err
}
password := base64.URLEncoding.EncodeToString(passwordBytes)
// Create the admin user
if err := NewUser(db, adminUsername, password, true); err != nil {
return err
}
// Print out the generated password
fmt.Printf("Generated password for admin user %s: %s\n", adminUsername, password)
return nil
}