From 2983c89b42516aa573a9aee651260d61590dd8b4 Mon Sep 17 00:00:00 2001 From: Sakurasan <26715255+Sakurasan@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:57:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20reset-admin-passwo?= =?UTF-8?q?rd=20=E5=91=BD=E4=BB=A4=E8=A1=8C=E9=87=8D=E7=BD=AE=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=91=98=E5=AF=86=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - cmd/server 子命令分发:reset-admin-password 不启动服务 - -password 留空随机生成 16 位(去除易混淆字符),打印一次 - -user 指定目标管理员,仅匹配 role=admin - 密码长度校验 8-72 与 API 一致,argon2id 参数取自配置 - Makefile 增加 make reset-admin-password 入口 --- Makefile | 5 +- server/cmd/server/main.go | 6 +++ server/cmd/server/reset_admin.go | 81 ++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 server/cmd/server/reset_admin.go diff --git a/Makefile b/Makefile index 94bbe46..6a57187 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,11 @@ -.PHONY: run build test tidy mock-upstream web-dev web-build +.PHONY: run build test tidy mock-upstream web-dev web-build reset-admin-password run: cd server && go run ./cmd/server +reset-admin-password: + cd server && go run ./cmd/server reset-admin-password + build: cd server && go build -o bin/openteam ./cmd/server diff --git a/server/cmd/server/main.go b/server/cmd/server/main.go index e92cffa..d6fe230 100644 --- a/server/cmd/server/main.go +++ b/server/cmd/server/main.go @@ -20,6 +20,12 @@ import ( ) func main() { + // 子命令分发:`openteam reset-admin-password ...`(无子命令时启动服务) + if len(os.Args) > 1 && os.Args[1] == "reset-admin-password" { + runResetAdminPassword(os.Args[2:]) + return + } + cfg, err := config.Load() if err != nil { log.Fatalf("config: %v", err) diff --git a/server/cmd/server/reset_admin.go b/server/cmd/server/reset_admin.go new file mode 100644 index 0000000..5c9cf4b --- /dev/null +++ b/server/cmd/server/reset_admin.go @@ -0,0 +1,81 @@ +// 子命令 reset-admin-password:重置管理员密码(忘记密码时用,需在 server/ 目录或配置可加载处执行)。 +// 用法:go run ./cmd/server reset-admin-password [-user admin] [-password 新密码] +package main + +import ( + "crypto/rand" + "flag" + "fmt" + "log" + "math/big" + + "github.com/openteam/server/internal/config" + "github.com/openteam/server/internal/pkg/crypto" + "github.com/openteam/server/internal/store" +) + +// pwAlphabet 密码随机串字符集(去掉 0/O/1/l/I 等易混淆字符)。 +const pwAlphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz" + +func runResetAdminPassword(args []string) { + fs := flag.NewFlagSet("reset-admin-password", flag.ExitOnError) + user := fs.String("user", "admin", "目标管理员用户名") + password := fs.String("password", "", "新密码(至少 8 位;留空则随机生成并打印一次)") + _ = fs.Parse(args) + + cfg, err := config.Load() + if err != nil { + log.Fatalf("config: %v", err) + } + db, err := store.Open(cfg.DB.Driver, cfg.DB.DSN) + if err != nil { + log.Fatalf("db: %v", err) + } + + var u store.User + if err := db.Where("username = ? AND role = ?", *user, store.RoleAdmin).First(&u).Error; err != nil { + log.Fatalf("admin user %q not found", *user) + } + + pw := *password + generated := false + if pw == "" { + pw, err = randomPassword(16) + if err != nil { + log.Fatalf("generate password: %v", err) + } + generated = true + } + if len(pw) < 8 || len(pw) > 72 { + log.Fatal("password length must be between 8 and 72") + } + + 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("hash password: %v", err) + } + if err := db.Model(&store.User{}).Where("id = ?", u.ID).Update("password_hash", hash).Error; err != nil { + log.Fatalf("update password: %v", err) + } + + if generated { + fmt.Printf("已重置 %q 的密码:%s\n", u.Username, pw) + fmt.Println("(请立即保存,不会再次显示)") + } else { + fmt.Printf("已重置 %q 的密码。\n", u.Username) + } +} + +// 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 +}