b8788c239e
MCP Docker / build-and-push (push) Has been cancelled
- 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
119 lines
2.9 KiB
Go
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
|
|
}
|