102 lines
2.5 KiB
Go
102 lines
2.5 KiB
Go
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"))
|
|
}
|