// Copyright (c) 2026 Zeni Kim // Use of this source code is governed by MIT-style // license that can be found in the LICENSE file. package cmd import ( "fmt" "os" "path/filepath" "strings" "github.com/joho/godotenv" "github.com/spf13/cobra" "golang.org/x/crypto/bcrypt" "gorm.io/driver/mysql" "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" ) var UserPasswordCmd = &cobra.Command{ Use: "user:password [username] [newpassword] [dev|prod]", Short: "Change a user password", Long: `Set the password for the user account with the specified name. Requires three arguments: username - the name of the user to update password - the new password to set env - "dev" to use .env-dev, "prod" to use .env Example: goffee user:password someuser 'strongpassword' dev goffee user:password admin 'newpass123' prod `, Args: cobra.ExactArgs(3), Run: func(cmd *cobra.Command, args []string) { username := args[0] newPassword := args[1] envMode := args[2] // Determine which env file to load var envFile string switch envMode { case "dev": envFile = ".env-dev" case "prod": envFile = ".env" default: fmt.Printf("Error: environment must be 'dev' or 'prod', got '%s'\n", envMode) return } // Build full path to env file envPath := filepath.Join(".", envFile) if _, err := os.Stat(envPath); os.IsNotExist(err) { fmt.Printf("Error: environment file '%s' not found in current directory\n", envFile) return } // Load env file err := godotenv.Load(envPath) if err != nil { fmt.Printf("Error loading environment file '%s': %v\n", envFile, err) return } // Connect to database db, err := connectDatabase() if err != nil { fmt.Printf("Error connecting to database: %v\n", err) return } // Find user by name type User struct { ID uint Name string Password string Email string } var user User result := db.Where("name = ?", username).First(&user) if result.Error != nil { if strings.Contains(result.Error.Error(), "record not found") { fmt.Printf("Error: user '%s' not found\n", username) } else { fmt.Printf("Error finding user: %v\n", result.Error) } return } // Hash the new password hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost) if err != nil { fmt.Printf("Error hashing password: %v\n", err) return } // Update password in database result = db.Model(&user).Update("password", string(hashedPassword)) if result.Error != nil { fmt.Printf("Error updating password: %v\n", result.Error) return } fmt.Printf("Password updated successfully for user '%s'\n", username) }, } func init() { rootCmd.AddCommand(UserPasswordCmd) } // connectDatabase establishes a database connection based on the loaded environment variables. func connectDatabase() (*gorm.DB, error) { dbDriver := os.Getenv("DB_DRIVER") var dialector gorm.Dialector switch dbDriver { case "sqlite": sqlitePath := os.Getenv("SQLITE_DB_PATH") if sqlitePath == "" { return nil, fmt.Errorf("SQLITE_DB_PATH environment variable not set") } dialector = sqlite.Open(sqlitePath) case "mysql": dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=True&loc=Local", os.Getenv("MYSQL_USERNAME"), os.Getenv("MYSQL_PASSWORD"), os.Getenv("MYSQL_HOST"), os.Getenv("MYSQL_PORT"), os.Getenv("MYSQL_DB_NAME"), os.Getenv("MYSQL_CHARSET"), ) dialector = mysql.Open(dsn) case "postgres": dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=%s TimeZone=%s", os.Getenv("POSTGRES_HOST"), os.Getenv("POSTGRES_USER"), os.Getenv("POSTGRES_PASSWORD"), os.Getenv("POSTGRES_DB_NAME"), os.Getenv("POSTGRES_PORT"), os.Getenv("POSTGRES_SSL_MODE"), os.Getenv("POSTGRES_TIMEZONE"), ) dialector = postgres.Open(dsn) default: return nil, fmt.Errorf("unsupported database driver: '%s'", dbDriver) } db, err := gorm.Open(dialector, &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) if err != nil { return nil, err } return db, nil }