加固后台管理安全
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
package adminauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"hfb_sys/backend/internal/model"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type BootstrapConfig struct {
|
||||
AppEnv string
|
||||
Username string
|
||||
Password string
|
||||
Nickname string
|
||||
}
|
||||
|
||||
// BootstrapAdmin 在管理员表为空时按部署配置创建首个超级管理员。
|
||||
func BootstrapAdmin(ctx context.Context, db *gorm.DB, cfg BootstrapConfig) error {
|
||||
if db == nil {
|
||||
return nil
|
||||
}
|
||||
var count int64
|
||||
if err := db.WithContext(ctx).Model(&model.AdminUser{}).Count(&count).Error; err != nil {
|
||||
if isAdminTableMissing(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
username := strings.TrimSpace(cfg.Username)
|
||||
password := cfg.Password
|
||||
if username == "" || password == "" {
|
||||
if strings.EqualFold(strings.TrimSpace(cfg.AppEnv), "production") {
|
||||
return errors.New("admin bootstrap credentials are required when no admin exists in production")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if strings.EqualFold(strings.TrimSpace(cfg.AppEnv), "production") && !bootstrapPasswordStrong(password) {
|
||||
return errors.New("ADMIN_BOOTSTRAP_PASSWORD must be at least 12 chars and include letters and digits in production")
|
||||
}
|
||||
nickname := strings.TrimSpace(cfg.Nickname)
|
||||
if nickname == "" {
|
||||
nickname = username
|
||||
}
|
||||
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
admin := model.AdminUser{
|
||||
Username: username,
|
||||
PasswordHash: string(hash),
|
||||
Nickname: nickname,
|
||||
Status: "active",
|
||||
TokenVersion: 1,
|
||||
PasswordMustChange: true,
|
||||
}
|
||||
if err := tx.Create(&admin).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
var superAdminRole model.Role
|
||||
if err := tx.Where("code = ?", "super_admin").First(&superAdminRole).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Create(&model.AdminUserRole{
|
||||
AdminID: admin.ID,
|
||||
RoleID: superAdminRole.ID,
|
||||
}).Error
|
||||
})
|
||||
}
|
||||
|
||||
func bootstrapPasswordStrong(value string) bool {
|
||||
if len([]rune(value)) < 12 {
|
||||
return false
|
||||
}
|
||||
hasLetter := false
|
||||
hasDigit := false
|
||||
for _, r := range value {
|
||||
if unicode.IsLetter(r) {
|
||||
hasLetter = true
|
||||
}
|
||||
if unicode.IsDigit(r) {
|
||||
hasDigit = true
|
||||
}
|
||||
}
|
||||
return hasLetter && hasDigit
|
||||
}
|
||||
|
||||
func isAdminTableMissing(err error) bool {
|
||||
message := strings.ToLower(err.Error())
|
||||
return strings.Contains(message, "admin_users") && (strings.Contains(message, "doesn't exist") || strings.Contains(message, "no such table"))
|
||||
}
|
||||
Reference in New Issue
Block a user