Files
SafeLine/management/webserver/model/user.go
T
Binbim b8788c239e
MCP Docker / build-and-push (push) Has been cancelled
feat: SafeLine CE 9.3.9 with pro features
- Remove telemetry: disabled all phone-home (installation notify, false positives, behavior tracking, upgrade tips)
- Version branding: removed 'ce-' prefix, product name changed to '长亭雷池 WAF'
- Detection engine enhancement: strict preset enables all 17 modules with aggressive configs
- Multi-user RBAC: admin/operator/viewer roles with password + TOTP 2FA
- Rate limiting: nginx limit_req per website (RPS, burst, action)
- JS Challenge: browser fingerprint verification page with cookie-based bypass
- Security fixes: removed InsecureSkipVerify, hardcoded credentials, NO_AUTH backdoor
2026-07-04 07:59:04 +08:00

119 lines
2.9 KiB
Go

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
}