Files
OpenFlare/cmd/reset_passwd.go
T
ryan 530a9dd3ee refactor(migration): delete pkg/migrator, move SQL to per-plugin embed
BREAKING: pkg/migrator/ deleted entirely. Migration SQL files are now
owned by each plugin in its own migrations/ directory.

Architecture:
- Delete pkg/migrator/ (26 global SQL files + ClickHouse migration)
- Move global SQL to per-plugin migrations/ with go:embed + Register()
- Rewrite cmd/app.go gooseEngine: uses Inject[DBService] for DB, iterates
  all plugin-registered MigrationEntry, runs goose.Up per entry
- core.MigrationEngine.Migrate signature changed: *Context instead of
  context.Context, so engine can resolve services via IoC

Per-plugin migration ownership:
  auth/             → w_access_tokens, w_auth_sources, w_external_accounts
  user/             → w_users (seed system user)
  admin/            → w_system_configs, w_templates (seeds)
  upload/           → w_uploads, w_upload_stats
  message_gateway/  → w_push_*, w_message_*
  risk_control/     → w_user_access_logs (PG + ClickHouse)
  driver_asynq_cron/  → w_schedules
  driver_asynq_worker/ → w_task_executions

Dependencies:
- cmd/banner.go: removed migration report display (migrations are automatic)
- cmd/reset_passwd.go: removed PreRun migrator.Migrate() call
- go.mod: clickhouse-go kept (used by plugins/infra/database/clickhouse.go)
2026-08-28 12:19:25 +08:00

124 lines
3.0 KiB
Go

// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"bufio"
"context"
"crypto/rand"
"errors"
"fmt"
"log"
"os"
"strings"
userdomain "github.com/Rain-kl/Wavelet/plugins/domain/user"
"github.com/Rain-kl/Wavelet/plugins/infra/database"
"github.com/Rain-kl/Wavelet/plugins/domain/auth"
"github.com/spf13/cobra"
"gorm.io/gorm"
)
var (
usernameFlag string
passwordFlag string
)
const (
passwdCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"
defaultPasswordLength = 16
)
func generateRandomPassword(length int) (string, error) {
bytes := make([]byte, length)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
for i, b := range bytes {
bytes[i] = passwdCharset[int(b)%len(passwdCharset)]
}
return string(bytes), nil
}
var resetPasswdCmd = &cobra.Command{
Use: "reset-passwd",
Short: "重置指定账号密码",
Run: func(_ *cobra.Command, _ []string) {
ctx := context.Background()
// Ensure database is initialized
database.DB(ctx)
var username string
if usernameFlag != "" {
username = usernameFlag
} else {
fmt.Print("请输入用户名: ")
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')
if err != nil {
log.Fatalf("读取用户名失败: %v\n", err)
}
username = strings.TrimSpace(input)
if username == "" {
log.Fatal("用户名不能为空\n")
}
}
user, err := userdomain.GetUserByUsername(ctx, username)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
log.Fatalf("错误: 用户 '%s' 不存在\n", username)
}
log.Fatalf("查询用户失败: %v\n", err)
}
var password string
if passwordFlag != "" {
password = passwordFlag
} else {
password, err = generateRandomPassword(defaultPasswordLength)
if err != nil {
log.Fatalf("生成随机密码失败: %v\n", err)
}
}
if err := user.SetEncryptedPassword(password); err != nil {
log.Fatalf("加密密码失败: %v\n", err)
}
err = database.DB(ctx).Transaction(func(tx *gorm.DB) error {
if err := tx.Model(&user).Update("password", user.Password).Error; err != nil {
return err
}
// Invalidate existing tokens
var tokens []userdomain.AccessToken
if err := tx.Where("user_id = ?", user.ID).Find(&tokens).Error; err == nil {
for _, token := range tokens {
auth.InvalidateCachedToken(ctx, token.TokenHash)
}
}
return tx.Where("user_id = ?", user.ID).Delete(&userdomain.AccessToken{}).Error
})
if err != nil {
log.Fatalf("重置密码失败: %v\n", err)
}
auth.InvalidateCachedUser(ctx, user.ID)
fmt.Println("成功重置密码!")
fmt.Printf("用户名: %s\n", user.Username)
fmt.Printf("新密码: %s\n", password)
},
}
func init() {
resetPasswdCmd.Flags().StringVar(&usernameFlag, "user", "", "重置密码的目标用户名")
resetPasswdCmd.Flags().StringVar(&passwordFlag, "password", "", "新密码(若不指定,则自动生成随机密码)")
rootCmd.AddCommand(resetPasswdCmd)
}