From ad2322d558a15ad901e149f324ba81af74911b42 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Wed, 9 Sep 2026 19:23:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(server):=20=E6=96=B0=E5=A2=9E=20passwd=20?= =?UTF-8?q?=E5=AD=90=E5=91=BD=E4=BB=A4=EF=BC=8C=E5=8F=AF=E9=87=8D=E7=BD=AE?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=AF=86=E7=A0=81=E5=B9=B6=E4=BD=9C=E5=BA=9F?= =?UTF-8?q?=E4=BC=9A=E8=AF=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/internal/auth/auth.go | 33 ++++++++++++++++++++ server/main.go | 7 +++++ server/passwd.go | 58 ++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 server/passwd.go diff --git a/server/internal/auth/auth.go b/server/internal/auth/auth.go index c5437ea..3eae201 100644 --- a/server/internal/auth/auth.go +++ b/server/internal/auth/auth.go @@ -169,6 +169,39 @@ func (s *Service) EnsureDefaultAdmin(ctx context.Context) (username, password st return username, password, true, nil } +// RandomPassword 生成 n 位随机密码,供首次初始化和 CLI 重置使用。 +func RandomPassword(n int) (string, error) { return randomPassword(n) } + +// SetPassword 重置用户密码并作废其全部会话;mustChange 表示下次登录后强制改密。 +func (s *Service) SetPassword(ctx context.Context, username, password string, mustChange bool) error { + if password == "" { + return fmt.Errorf("密码不能为空") + } + hash, err := bcrypt.GenerateFromPassword([]byte(password), s.bcryptCost) + if err != nil { + return fmt.Errorf("生成密码哈希: %w", err) + } + must := 0 + if mustChange { + must = 1 + } + res, err := s.db.ExecContext(ctx, + `UPDATE users SET password_hash = ?, must_change_password = ?, updated_at = datetime('now') WHERE username = ?`, + string(hash), must, username) + if err != nil { + return fmt.Errorf("更新密码: %w", err) + } + if n, err := res.RowsAffected(); err == nil && n == 0 { + return fmt.Errorf("用户 %s 不存在", username) + } + + var id int64 + if err := s.db.QueryRowContext(ctx, `SELECT id FROM users WHERE username = ?`, username).Scan(&id); err != nil { + return fmt.Errorf("查询用户: %w", err) + } + return s.RevokeUserSessions(ctx, id) +} + // Authenticate 校验用户名密码并创建会话,返回用户与不落库的明文 token。 func (s *Service) Authenticate(ctx context.Context, username, password, ip, userAgent string) (*User, string, error) { if !s.AllowLogin(ip) { diff --git a/server/main.go b/server/main.go index d0c029f..94b182a 100644 --- a/server/main.go +++ b/server/main.go @@ -36,6 +36,13 @@ func main() { } return } + if len(os.Args) > 1 && os.Args[1] == "passwd" { + if err := runPasswd(os.Args[2:]); err != nil { + fmt.Fprintf(os.Stderr, "重置密码失败: %v\n", err) + os.Exit(1) + } + return + } configPath := flag.String("config", "config.yaml", "配置文件路径") project := flag.String("project", "default", "赛事项目名(data/event_projs/<项目名>/event.db)") diff --git a/server/passwd.go b/server/passwd.go new file mode 100644 index 0000000..006b8c9 --- /dev/null +++ b/server/passwd.go @@ -0,0 +1,58 @@ +package main + +import ( + "context" + "flag" + "fmt" + + "git.yeij.top/AskaEth/Scoreboard/server/internal/auth" + "git.yeij.top/AskaEth/Scoreboard/server/internal/config" + "git.yeij.top/AskaEth/Scoreboard/server/internal/store" +) + +// runPasswd 实现 `scoreboard passwd` 子命令:重置用户密码并作废其全部会话。 +// 初始密码是首次启动随机生成的,只能用日志找回;忘了就用这个命令重置。 +func runPasswd(args []string) error { + fs := flag.NewFlagSet("passwd", flag.ExitOnError) + configPath := fs.String("config", "config.yaml", "配置文件路径") + username := fs.String("user", "admin", "用户名") + password := fs.String("password", "", "新密码;留空则随机生成并打印") + if err := fs.Parse(args); err != nil { + return err + } + + cfg, err := config.Load(*configPath) + if err != nil { + return err + } + + st, err := store.Open(cfg.DataDir) + if err != nil { + return err + } + defer func() { _ = st.Close() }() + if err := st.Migrate(); err != nil { + return err + } + + pwd := *password + generated := pwd == "" + if generated { + pwd, err = auth.RandomPassword(16) + if err != nil { + return err + } + } + + svc := auth.New(st.DB(), auth.Options{}) + if err := svc.SetPassword(context.Background(), *username, pwd, generated); err != nil { + return err + } + + if generated { + fmt.Printf("已重置用户 %s 的密码:%s\n请登录后立即修改(已要求下次登录强制改密)。\n", *username, pwd) + } else { + fmt.Printf("已重置用户 %s 的密码。\n", *username) + } + return nil +}