Files
GoMFT/internal/db/user.go
T

167 lines
4.6 KiB
Go

package db
import (
"fmt"
"time"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)
// User represents a user account in the system
type User struct {
ID uint `gorm:"primarykey"`
Email string `gorm:"unique;not null"`
PasswordHash string `gorm:"not null"`
IsAdmin *bool `gorm:"default:false"`
LastPasswordChange time.Time
FailedLoginAttempts int `gorm:"default:0"`
AccountLocked *bool `gorm:"default:false"`
LockoutUntil *time.Time
Theme string `gorm:"default:'light'"`
TwoFactorSecret string `gorm:"type:varchar(32)"`
TwoFactorEnabled bool `gorm:"default:false"`
BackupCodes string `gorm:"type:text"` // Comma-separated backup codes
Roles []Role `gorm:"many2many:user_roles"`
CreatedAt time.Time
UpdatedAt time.Time
}
// PasswordHistory stores previous passwords for a user
type PasswordHistory struct {
ID uint `gorm:"primarykey"`
UserID uint `gorm:"not null"`
User User `gorm:"foreignkey:UserID"`
PasswordHash string `gorm:"not null"`
CreatedAt time.Time
}
// PasswordResetToken stores tokens for password reset requests
type PasswordResetToken struct {
ID uint `gorm:"primarykey"`
UserID uint `gorm:"not null"`
User User `gorm:"foreignkey:UserID"`
Token string `gorm:"not null"`
ExpiresAt time.Time `gorm:"not null"`
Used *bool `gorm:"default:false"`
CreatedAt time.Time
UpdatedAt time.Time
}
// --- User Helper Methods ---
// GetIsAdmin returns the value of IsAdmin with a default if nil
func (u *User) GetIsAdmin() bool {
if u.IsAdmin == nil {
return false // Default to false if not set
}
return *u.IsAdmin
}
// SetIsAdmin sets the IsAdmin field
func (u *User) SetIsAdmin(value bool) {
u.IsAdmin = &value
}
// GetAccountLocked returns the value of AccountLocked with a default if nil
func (u *User) GetAccountLocked() bool {
if u.AccountLocked == nil {
return false // Default to false if not set
}
return *u.AccountLocked
}
// SetAccountLocked sets the AccountLocked field
func (u *User) SetAccountLocked(value bool) {
u.AccountLocked = &value
}
// HasRole checks if the user has a specific role
func (u *User) HasRole(roleName string) bool {
for _, role := range u.Roles {
if role.Name == roleName {
return true
}
}
return false
}
// HasPermission checks if the user has a specific permission through any of their roles
func (u *User) HasPermission(permission string) bool {
for _, role := range u.Roles {
if role.HasPermission(permission) {
return true
}
}
return false
}
// GetRoles returns all roles assigned to the user
// Note: This requires preloading Roles when fetching the user
func (u *User) GetRoles(tx *gorm.DB) ([]Role, error) {
var roles []Role
err := tx.Model(u).Association("Roles").Find(&roles)
return roles, err
}
// AssignRole assigns a role to the user
func (u *User) AssignRole(tx *gorm.DB, roleID uint, assignedByID uint) error {
var role Role
if err := tx.First(&role, roleID).Error; err != nil {
return err
}
// Assuming Role struct has AssignToUser method (from role.go)
return role.AssignToUser(tx, u.ID, assignedByID)
}
// UnassignRole removes a role from the user
func (u *User) UnassignRole(tx *gorm.DB, roleID uint, unassignedByID uint) error {
var role Role
if err := tx.First(&role, roleID).Error; err != nil {
return err
}
// Assuming Role struct has UnassignFromUser method (from role.go)
return role.UnassignFromUser(tx, u.ID, unassignedByID)
}
// SetPassword sets the user's password with secure hashing
func (u *User) SetPassword(password string) error {
// Validate password length
if len(password) < 8 {
return fmt.Errorf("password must be at least 8 characters long")
}
// Hash the password using bcrypt
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return fmt.Errorf("failed to hash password: %w", err)
}
// Store the hashed password
u.PasswordHash = string(hashedPassword)
u.LastPasswordChange = time.Now()
return nil
}
// CheckPassword verifies if the provided password matches the stored hash
func (u *User) CheckPassword(password string) bool {
err := bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte(password))
return err == nil
}
// --- PasswordResetToken Helper Methods ---
// GetUsed returns the value of Used with a default if nil
func (t *PasswordResetToken) GetUsed() bool {
if t.Used == nil {
return false // Default to false if not set
}
return *t.Used
}
// SetUsed sets the Used field
func (t *PasswordResetToken) SetUsed(value bool) {
t.Used = &value
}