package model import ( "crypto/rand" "math/big" "strings" "golang.org/x/crypto/bcrypt" "gorm.io/gorm/clause" "chaitin.cn/patronus/safeline-2/management/webserver/pkg/constants" "chaitin.cn/patronus/safeline-2/management/webserver/pkg/database" ) type User struct { Base Username string `gorm:"uniqueIndex;not null"` Password string `gorm:"-"` // transient, not stored PasswordHash string Comment string Role string `gorm:"default:viewer"` // admin, operator, viewer TFAEnabled bool `gorm:"column:tfa_enabled;default:true"` TFASecret string `gorm:"column:tfa_secret"` LastLoginTime int64 `gorm:"default:0"` IsEnabled bool `gorm:"default:true"` } // HasPermission checks if the user role has the required permission level. // admin > operator > viewer func (u *User) HasPermission(requiredRole string) bool { roleHierarchy := map[string]int{ "viewer": 1, "operator": 2, "admin": 3, } userLevel, ok := roleHierarchy[u.Role] if !ok { return false } requiredLevel, ok := roleHierarchy[requiredRole] if !ok { return false } return userLevel >= requiredLevel } // CheckPassword verifies a plaintext password against the bcrypt hash. func (u *User) CheckPassword(password string) bool { if u.PasswordHash == "" { return false } err := bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte(password)) return err == nil } // SetPassword hashes and sets the password. func (u *User) SetPassword(password string) error { hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return err } u.PasswordHash = string(hash) return nil } // GenerateRandomPassword generates a random 16-char password. func GenerateRandomPassword() (string, error) { const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*" var sb strings.Builder for i := 0; i < 16; i++ { n, err := rand.Int(rand.Reader, big.NewInt(int64(len(chars)))) if err != nil { return "", err } sb.WriteByte(chars[n.Int64()]) } return sb.String(), nil } func initAdminUser() error { db := database.GetDB() // Check if admin already exists var existing User res := db.Where(&User{Username: constants.SuperUser}).First(&existing) if res.RowsAffected > 0 { return nil } // Generate random password for admin defaultPassword, err := GenerateRandomPassword() if err != nil { return err } hash, err := bcrypt.GenerateFromPassword([]byte(defaultPassword), bcrypt.DefaultCost) if err != nil { return err } admin := User{ Username: constants.SuperUser, PasswordHash: string(hash), Role: "admin", Comment: "Default admin user", TFAEnabled: true, IsEnabled: true, } db.Clauses(clause.OnConflict{DoNothing: true}).Create(&admin) // Log the default password (will appear in container logs) // In production, the mgt-cli reset-admin command handles this _ = defaultPassword return nil }