// Package cli 命令行入口。 package cli import ( "crypto/rand" "fmt" "log" "math/big" "os" "strings" "github.com/openteam/server/internal/config" "github.com/openteam/server/internal/pkg/crypto" "github.com/openteam/server/internal/store" "github.com/spf13/cobra" "golang.org/x/term" "gorm.io/gorm" ) // pwAlphabet 密码随机串字符集(去掉 0/O/1/l/I 等易混淆字符)。 const pwAlphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz" var ( resetUser string resetPassword string ) var resetAdminPasswordCmd = &cobra.Command{ Use: "reset-admin-password", Short: "重置管理员密码", Long: `重置管理员密码(忘记密码时用,需在可加载配置的目录下执行)。 交互式引导(默认): go run ./cmd/server reset-admin-password 非交互式(用于脚本): go run ./cmd/server reset-admin-password --user admin --password "新密码"`, Run: func(cmd *cobra.Command, args []string) { runResetAdminPassword() }, } func init() { rootCmd.AddCommand(resetAdminPasswordCmd) resetAdminPasswordCmd.Flags().StringVar(&resetUser, "user", "", "目标管理员用户名(留空则交互选择)") resetAdminPasswordCmd.Flags().StringVar(&resetPassword, "password", "", "新密码,至少 8 位(留空则交互输入)") } func runResetAdminPassword() { // --- 阶段 1:加载配置与数据库 --- cfg, err := config.Load() if err != nil { log.Fatalf("加载配置失败: %v", err) } db, err := store.Open(cfg.DB.Driver, cfg.DB.DSN) if err != nil { log.Fatalf("连接数据库失败: %v", err) } // --- 阶段 2:列出管理员 --- var admins []struct { ID uint64 Username string } db.Model(&store.User{}).Select("id, username").Where("role = ?", store.RoleAdmin).Find(&admins) if len(admins) == 0 { log.Fatalf("系统中没有管理员用户") } // --- 阶段 3:选择目标管理员 --- target := targetAdmin(db, admins) // --- 阶段 4:获取新密码 --- var pw string if resetPassword != "" { pw = resetPassword } else { pw = promptPassword() } if pw == "" { log.Fatalf("密码不能为空") } if len(pw) < 8 || len(pw) > 72 { log.Fatalf("密码长度必须在 8 到 72 位之间") } // --- 阶段 5:加密并更新 --- hasher := crypto.NewPasswordHasher(cfg.Auth.Argon2Time, cfg.Auth.Argon2Memory, cfg.Auth.Argon2Threads, cfg.Auth.Argon2KeyLen, cfg.Auth.SaltLen) hash, err := hasher.HashPassword(pw) if err != nil { log.Fatalf("加密密码失败: %v", err) } if err := db.Model(&store.User{}).Where("id = ?", target.ID).Update("password_hash", hash).Error; err != nil { log.Fatalf("更新密码失败: %v", err) } fmt.Printf("✅ 已重置管理员 %q 的密码!\n", target.Username) } // targetAdmin 解析 --user 或交互选择目标管理员。 func targetAdmin(db *gorm.DB, admins []struct { ID uint64 Username string }) *store.User { if resetUser != "" { for i := range admins { if admins[i].Username == resetUser { return &store.User{ID: admins[i].ID, Username: admins[i].Username} } } log.Fatalf("管理员 %q 不存在", resetUser) } if len(admins) == 1 { return &store.User{ID: admins[0].ID, Username: admins[0].Username} } return selectAdmin(admins) } // selectAdmin 交互式选择管理员用户。 func selectAdmin(admins []struct { ID uint64 Username string }) *store.User { fmt.Println("检测到多个管理员用户,请选择要重置密码的目标:") for i, a := range admins { fmt.Printf(" [%d] %s\n", i+1, a.Username) } fmt.Printf("请输入编号 (1-%d): ", len(admins)) var n int if _, err := fmt.Scanf("%d", &n); err != nil || n < 1 || n > len(admins) { log.Fatalf("无效输入") } return &store.User{ID: admins[n-1].ID, Username: admins[n-1].Username} } // promptPassword 交互式输入密码,并提供两种方式选择。 func promptPassword() string { fmt.Println("请选择密码设置方式:") fmt.Println(" [1] 手动输入密码") fmt.Println(" [2] 自动生成随机密码(16 位,不含易混淆字符)") fmt.Printf("请输入编号 (1-2): ") var n int if _, err := fmt.Scanf("%d", &n); err != nil || n < 1 || n > 2 { log.Fatalf("无效输入,请输入 1 或 2") } if n == 2 { pw, err := randomPassword(16) if err != nil { log.Fatalf("生成随机密码失败: %v", err) } fmt.Printf("🔑 生成的随机密码:%s\n", pw) fmt.Println("(请立即保存,不会再次显示)") return pw } // 手动输入:静默输入(不回显) fmt.Print("请输入新密码(至少 8 位):") raw, err := term.ReadPassword(int(os.Stdin.Fd())) fmt.Println() if err != nil { log.Fatalf("读取密码失败: %v", err) } return strings.TrimSpace(string(raw)) } // randomPassword 用 crypto/rand 从 pwAlphabet 生成 n 位随机密码。 func randomPassword(n int) (string, error) { buf := make([]byte, n) for i := range buf { k, err := rand.Int(rand.Reader, big.NewInt(int64(len(pwAlphabet)))) if err != nil { return "", err } buf[i] = pwAlphabet[k.Int64()] } return string(buf), nil }