init bootstrap
This commit is contained in:
318
local/model/user.go
Normal file
318
local/model/user.go
Normal file
@@ -0,0 +1,318 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user