107 lines
2.6 KiB
Go
107 lines
2.6 KiB
Go
package models
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"golang.org/x/crypto/bcrypt"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// AuthUser represents a local user for the built-in SSO system
|
|
type AuthUser struct {
|
|
ID uint `gorm:"primarykey" json:"id"`
|
|
UUID string `gorm:"uniqueIndex;not null" json:"uuid"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
|
|
// User identification
|
|
Username string `gorm:"uniqueIndex;not null" json:"username"`
|
|
Email string `gorm:"uniqueIndex;not null" json:"email"`
|
|
Name string `json:"name"` // Full name for display
|
|
|
|
// Authentication
|
|
PasswordHash string `gorm:"not null" json:"-"` // Never expose in JSON
|
|
Enabled bool `gorm:"default:true" json:"enabled"`
|
|
|
|
// Additional emails for linking identities (comma-separated)
|
|
AdditionalEmails string `json:"additional_emails"`
|
|
|
|
// Authorization
|
|
Roles string `json:"roles"` // Comma-separated roles (e.g., "admin,user")
|
|
|
|
// MFA
|
|
MFAEnabled bool `json:"mfa_enabled"`
|
|
MFASecret string `json:"-"` // TOTP secret, never expose
|
|
|
|
// Metadata
|
|
LastLoginAt *time.Time `json:"last_login_at,omitempty"`
|
|
}
|
|
|
|
// BeforeCreate generates UUID for new auth users
|
|
func (u *AuthUser) BeforeCreate(tx *gorm.DB) error {
|
|
if u.UUID == "" {
|
|
u.UUID = uuid.New().String()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetPassword hashes and sets the user's password
|
|
func (u *AuthUser) SetPassword(password string) error {
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
u.PasswordHash = string(hash)
|
|
return nil
|
|
}
|
|
|
|
// CheckPassword verifies a password against the stored hash
|
|
func (u *AuthUser) CheckPassword(password string) bool {
|
|
err := bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte(password))
|
|
return err == nil
|
|
}
|
|
|
|
// HasRole checks if the user has a specific role
|
|
func (u *AuthUser) HasRole(role string) bool {
|
|
if u.Roles == "" {
|
|
return false
|
|
}
|
|
// Simple contains check for comma-separated roles
|
|
for _, r := range splitRoles(u.Roles) {
|
|
if r == role {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// splitRoles splits comma-separated roles string
|
|
func splitRoles(roles string) []string {
|
|
if roles == "" {
|
|
return []string{}
|
|
}
|
|
var result []string
|
|
for i := 0; i < len(roles); {
|
|
start := i
|
|
for i < len(roles) && roles[i] != ',' {
|
|
i++
|
|
}
|
|
if start < i {
|
|
role := roles[start:i]
|
|
// Trim spaces
|
|
for len(role) > 0 && role[0] == ' ' {
|
|
role = role[1:]
|
|
}
|
|
for len(role) > 0 && role[len(role)-1] == ' ' {
|
|
role = role[:len(role)-1]
|
|
}
|
|
if role != "" {
|
|
result = append(result, role)
|
|
}
|
|
}
|
|
i++ // Skip comma
|
|
}
|
|
return result
|
|
}
|