package model import ( "errors" "omega-server/local/utl/password" "regexp" "strings" "time" "gorm.io/gorm" ) // User represents a user in the system type User struct { BaseModel Email string `json:"email" gorm:"unique;not null;type:varchar(255)"` Username string `json:"username" gorm:"unique;not null;type:varchar(100)"` Name string `json:"name" gorm:"not null;type:varchar(255)"` PasswordHash string `json:"-" gorm:"not null;type:text"` Active bool `json:"active" gorm:"default:true"` EmailVerified bool `json:"emailVerified" gorm:"default:false"` EmailVerificationToken string `json:"-" gorm:"type:varchar(255)"` PasswordResetToken string `json:"-" gorm:"type:varchar(255)"` PasswordResetExpires *time.Time `json:"-"` LastLogin *time.Time `json:"lastLogin"` LoginAttempts int `json:"-" gorm:"default:0"` LockedUntil *time.Time `json:"-"` TwoFactorEnabled bool `json:"twoFactorEnabled" gorm:"default:false"` TwoFactorSecret string `json:"-" gorm:"type:varchar(255)"` Roles []Role `json:"roles" gorm:"many2many:user_roles;"` AuditLogs []AuditLog `json:"-" gorm:"foreignKey:UserID"` } // UserCreateRequest represents the request to create a new user type UserCreateRequest struct { Email string `json:"email" validate:"required,email"` Username string `json:"username" validate:"required,min=3,max=50"` Name string `json:"name" validate:"required,min=2,max=100"` Password string `json:"password" validate:"required,min=8"` RoleIDs []string `json:"roleIds"` } // UserUpdateRequest represents the request to update a user type UserUpdateRequest struct { Email *string `json:"email,omitempty" validate:"omitempty,email"` Username *string `json:"username,omitempty" validate:"omitempty,min=3,max=50"` Name *string `json:"name,omitempty" validate:"omitempty,min=2,max=100"` Active *bool `json:"active,omitempty"` RoleIDs []string `json:"roleIds,omitempty"` } // UserLoginRequest represents a login request type UserLoginRequest struct { Email string `json:"email" validate:"required,email"` Password string `json:"password" validate:"required"` } // UserLoginResponse represents a login response type UserLoginResponse struct { Token string `json:"token"` RefreshToken string `json:"refreshToken"` ExpiresAt time.Time `json:"expiresAt"` User UserInfo `json:"user"` } // UserInfo represents public user information type UserInfo struct { ID string `json:"id"` Email string `json:"email"` Username string `json:"username"` Name string `json:"name"` Active bool `json:"active"` EmailVerified bool `json:"emailVerified"` LastLogin *time.Time `json:"lastLogin"` Roles []RoleInfo `json:"roles"` Permissions []string `json:"permissions"` DateCreated time.Time `json:"dateCreated"` } // ChangePasswordRequest represents a password change request type ChangePasswordRequest struct { CurrentPassword string `json:"currentPassword" validate:"required"` NewPassword string `json:"newPassword" validate:"required,min=8"` } // ResetPasswordRequest represents a password reset request type ResetPasswordRequest struct { Email string `json:"email" validate:"required,email"` } // ResetPasswordConfirmRequest represents a password reset confirmation type ResetPasswordConfirmRequest struct { Token string `json:"token" validate:"required"` NewPassword string `json:"newPassword" validate:"required,min=8"` } // BeforeCreate is called before creating a user func (u *User) BeforeCreate(tx *gorm.DB) error { u.BaseModel.BeforeCreate() // Normalize email and username u.Email = strings.ToLower(strings.TrimSpace(u.Email)) u.Username = strings.ToLower(strings.TrimSpace(u.Username)) u.Name = strings.TrimSpace(u.Name) return u.Validate() } // BeforeUpdate is called before updating a user func (u *User) BeforeUpdate(tx *gorm.DB) error { u.BaseModel.BeforeUpdate() // Normalize fields if they're being updated if u.Email != "" { u.Email = strings.ToLower(strings.TrimSpace(u.Email)) } if u.Username != "" { u.Username = strings.ToLower(strings.TrimSpace(u.Username)) } if u.Name != "" { u.Name = strings.TrimSpace(u.Name) } return u.Validate() } // Validate validates user data func (u *User) Validate() error { if u.Email == "" { return errors.New("email is required") } if !isValidEmail(u.Email) { return errors.New("invalid email format") } if u.Username == "" { return errors.New("username is required") } if len(u.Username) < 3 || len(u.Username) > 50 { return errors.New("username must be between 3 and 50 characters") } if !isValidUsername(u.Username) { return errors.New("username can only contain letters, numbers, underscores, and hyphens") } if u.Name == "" { return errors.New("name is required") } if len(u.Name) < 2 || len(u.Name) > 100 { return errors.New("name must be between 2 and 100 characters") } return nil } // SetPassword sets the user's password hash func (u *User) SetPassword(plainPassword string) error { if err := validatePassword(plainPassword); err != nil { return err } hash, err := password.HashPassword(plainPassword) if err != nil { return err } u.PasswordHash = hash return nil } // CheckPassword verifies the user's password func (u *User) CheckPassword(plainPassword string) bool { return password.CheckPasswordHash(plainPassword, u.PasswordHash) } // VerifyPassword verifies the user's password (alias for CheckPassword) func (u *User) VerifyPassword(plainPassword string) bool { return u.CheckPassword(plainPassword) } // IsLocked checks if the user account is locked func (u *User) IsLocked() bool { if u.LockedUntil == nil { return false } return time.Now().Before(*u.LockedUntil) } // Lock locks the user account for the specified duration func (u *User) Lock(duration time.Duration) { lockUntil := time.Now().Add(duration) u.LockedUntil = &lockUntil } // Unlock unlocks the user account func (u *User) Unlock() { u.LockedUntil = nil u.LoginAttempts = 0 } // IncrementLoginAttempts increments the login attempt counter func (u *User) IncrementLoginAttempts() { u.LoginAttempts++ } // ResetLoginAttempts resets the login attempt counter func (u *User) ResetLoginAttempts() { u.LoginAttempts = 0 } // UpdateLastLogin updates the last login timestamp func (u *User) UpdateLastLogin() { now := time.Now() u.LastLogin = &now } // ToUserInfo converts User to UserInfo (public information) func (u *User) ToUserInfo() UserInfo { userInfo := UserInfo{ ID: u.ID, Email: u.Email, Username: u.Username, Name: u.Name, Active: u.Active, EmailVerified: u.EmailVerified, LastLogin: u.LastLogin, DateCreated: u.DateCreated, Roles: make([]RoleInfo, len(u.Roles)), Permissions: []string{}, } // Convert roles and collect permissions permissionSet := make(map[string]bool) for i, role := range u.Roles { userInfo.Roles[i] = role.ToRoleInfo() for _, permission := range role.Permissions { permissionSet[permission.Name] = true } } // Convert permission set to slice for permission := range permissionSet { userInfo.Permissions = append(userInfo.Permissions, permission) } return userInfo } // 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 func (u *User) HasPermission(permissionName string) bool { for _, role := range u.Roles { for _, permission := range role.Permissions { if permission.Name == permissionName { return true } } } return false } // isValidEmail validates email format func isValidEmail(email string) bool { emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`) return emailRegex.MatchString(email) } // isValidUsername validates username format func isValidUsername(username string) bool { usernameRegex := regexp.MustCompile(`^[a-zA-Z0-9_-]+$`) return usernameRegex.MatchString(username) } // validatePassword validates password strength func validatePassword(password string) error { if len(password) < 8 { return errors.New("password must be at least 8 characters long") } if len(password) > 128 { return errors.New("password must not exceed 128 characters") } // Check for at least one lowercase letter if matched, _ := regexp.MatchString(`[a-z]`, password); !matched { return errors.New("password must contain at least one lowercase letter") } // Check for at least one uppercase letter if matched, _ := regexp.MatchString(`[A-Z]`, password); !matched { return errors.New("password must contain at least one uppercase letter") } // Check for at least one digit if matched, _ := regexp.MatchString(`\d`, password); !matched { return errors.New("password must contain at least one digit") } // Check for at least one special character if matched, _ := regexp.MatchString(`[!@#$%^&*(),.?":{}|<>]`, password); !matched { return errors.New("password must contain at least one special character") } return nil }