/* * Copyright 2026 Oleg Borodin * * This work is published and licensed under a Creative Commons * Attribution-NonCommercial-NoDerivatives 4.0 International License. * * Distribution of this work is permitted, but commercial use and * modifications are strictly prohibited. */ package main import ( "context" "regexp" "strings" "time" "github.com/spf13/cobra" "github.com/spf13/viper" "mstore/pkg/client" "mstore/pkg/descr" "mstore/pkg/terms" ) const ( uuidRegex = `^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$` defaultHostname = "localhost:1025" ) type AccountUtil struct { createAccountParams CreateAccountParams updateAccountParams UpdateAccountParams getAccountParams GetAccountParams deleteAccountParams DeleteAccountParams listAccountsParams ListAccountsParams commonAccountParams CommonAccountParams } type CommonAccountParams struct { Username string Password string Hostname string Timeout uint64 } func (util *AccountUtil) CreateAccountCmds() *cobra.Command { var subCmd = &cobra.Command{ Use: "accounts", Short: "Account operations", Aliases: []string{"account"}, } const defaultTimeout uint64 = 10 subCmd.PersistentFlags().StringVarP(&util.commonAccountParams.Username, "user", "u", util.commonAccountParams.Username, "Username") subCmd.PersistentFlags().StringVarP(&util.commonAccountParams.Password, "pass", "p", util.commonAccountParams.Password, "Password") subCmd.PersistentFlags().StringVarP(&util.commonAccountParams.Hostname, "host", "x", defaultHostname, "Hostname") subCmd.PersistentFlags().Uint64VarP(&util.commonAccountParams.Timeout, "timeout", "t", defaultTimeout, "Operation timeout") subCmd.MarkFlagsRequiredTogether("user", "pass") vi := viper.New() vi.SetEnvPrefix("mstore") vi.BindEnv("user") vi.BindEnv("pass") util.commonAccountParams.Username = vi.GetString("user") util.commonAccountParams.Password = vi.GetString("pass") // CreateAccount var createAccountCmd = &cobra.Command{ Use: "create [user:pass@]hostname[:port] username password", Short: "Create user account", Args: cobra.ExactArgs(3), Run: util.CreateAccount, } subCmd.AddCommand(createAccountCmd) // GetAccount var getAccountCmd = &cobra.Command{ Use: "get [user:pass@]hostname[:port] accountId|username", Short: "Get account info", Args: cobra.ExactArgs(2), Run: util.GetAccount, } subCmd.AddCommand(getAccountCmd) // UpdateAccount var updateAccountCmd = &cobra.Command{ Use: "update [user:pass@]hostname[:port] username|accounId", Short: "Update account parameters", Args: cobra.ExactArgs(2), Run: util.UpdateAccount, } updateAccountCmd.Flags().StringVarP(&util.updateAccountParams.NewUsername, "newname", "N", "", "New username") updateAccountCmd.Flags().StringVarP(&util.updateAccountParams.NewPassword, "newpass", "P", "", "New password") updateAccountCmd.MarkFlagsOneRequired("newname", "newpass") subCmd.AddCommand(updateAccountCmd) // DeleteAccount var deleteAccountCmd = &cobra.Command{ Use: "delete [user:pass@]hostname[:port] username|accountId", Short: "Delete account", Args: cobra.ExactArgs(2), Run: util.DeleteAccount, } deleteAccountCmd.Flags().StringVarP(&util.updateAccountParams.AccountID, "name", "n", "", "Account ID or username") subCmd.AddCommand(deleteAccountCmd) // ListAccount var listAccountsCmd = &cobra.Command{ Use: "list [user:pass@]hostname[:port]", Short: "list accounts", Args: cobra.ExactArgs(1), Run: util.ListAccounts, } listAccountsCmd.Flags().BoolVarP(&util.listAccountsParams.Detail, "detail", "d", false, "Show detail information") listAccountsCmd.Flags().StringVarP(&util.listAccountsParams.Regex, "regex", "r", "", "Output regexp for usernames") subCmd.AddCommand(listAccountsCmd) return subCmd } // CreateAccount type CreateAccountParams struct { NewUsername string NewPassword string } type CreateAccountResult struct { AccountID string `yaml:"accountId"` Grants map[string]string `yaml:"grantsIds,omitempty"` } func (util *AccountUtil) CreateAccount(cmd *cobra.Command, args []string) { util.commonAccountParams.Hostname = args[0] util.createAccountParams.NewUsername = args[1] util.createAccountParams.NewPassword = args[2] res, err := util.createAccount(&util.commonAccountParams, &util.createAccountParams) printResponse(res, err) } func (util *AccountUtil) createAccount(common *CommonAccountParams, params *CreateAccountParams) (*CreateAccountResult, error) { var err error res := &CreateAccountResult{ Grants: make(map[string]string, 0), } hostname, err := packUserinfo(common.Hostname, common.Username, common.Password) if err != nil { return res, err } timeout := time.Duration(common.Timeout) * time.Second ctx, _ := context.WithTimeout(context.Background(), timeout) accountID, err := client.NewClient().CreateAccount(ctx, hostname, params.NewUsername, params.NewPassword) if err != nil { return res, err } fullRights := []string{ terms.RightWriteAccounts, terms.RightReadAccounts, terms.RightWriteFiles, terms.RightReadFiles, terms.RightWriteImages, terms.RightReadImages, } for _, right := range fullRights { id, err := client.NewClient().CreateGrantByAccountID(ctx, hostname, accountID, right, ".*") if err != nil { return res, err } res.Grants[id] = right } res.AccountID = accountID return res, err } // UpdateAccount type UpdateAccountParams struct { Timeout uint64 AccountID string NewUsername string NewPassword string } type UpdateAccountResult struct { File *descr.File `yaml:"file,omitempty"` } func (util *AccountUtil) UpdateAccount(cmd *cobra.Command, args []string) { util.commonAccountParams.Hostname = args[0] util.updateAccountParams.AccountID = args[1] res, err := util.updateAccount(&util.commonAccountParams, &util.updateAccountParams) printResponse(res, err) } func (util *AccountUtil) updateAccount(common *CommonAccountParams, params *UpdateAccountParams) (*UpdateAccountResult, error) { var err error res := &UpdateAccountResult{} hostname, err := packUserinfo(common.Hostname, common.Username, common.Password) if err != nil { return res, err } timeout := time.Duration(common.Timeout) * time.Second ctx, _ := context.WithTimeout(context.Background(), timeout) re := regexp.MustCompile(uuidRegex) id := strings.ToLower(params.AccountID) if re.MatchString(id) { err = client.NewClient().UpdateAccountByID(ctx, hostname, id, params.NewUsername, params.NewPassword) } else { err = client.NewClient().UpdateAccountByName(ctx, hostname, params.AccountID, params.NewUsername, params.NewPassword) } if err != nil { return res, err } return res, err } // Get file type GetAccountParams struct { Timeout uint64 AccountID string } func (util *AccountUtil) GetAccount(cmd *cobra.Command, args []string) { util.commonAccountParams.Hostname = args[0] util.getAccountParams.AccountID = args[1] res, err := util.getAccount(&util.commonAccountParams, &util.getAccountParams) printResponse(res, err) } type GetAccountResult struct { Account *descr.AccountShort `yaml:"account,omitempty"` } func (util *AccountUtil) getAccount(common *CommonAccountParams, params *GetAccountParams) (*GetAccountResult, error) { var err error res := &GetAccountResult{} hostname, err := packUserinfo(common.Hostname, common.Username, common.Password) if err != nil { return res, err } timeout := time.Duration(common.Timeout) * time.Second ctx, _ := context.WithTimeout(context.Background(), timeout) opRes := &descr.AccountShort{} re := regexp.MustCompile(uuidRegex) id := strings.ToLower(params.AccountID) if re.MatchString(id) { opRes, err = client.NewClient().GetAccountByID(ctx, hostname, id) } else { opRes, err = client.NewClient().GetAccountByName(ctx, hostname, params.AccountID) } if err != nil { return res, err } res.Account = opRes return res, err } // DeleteAccount type DeleteAccountParams struct { AccountID string Timeout uint64 } type DeleteAccountResult struct{} func (util *AccountUtil) DeleteAccount(cmd *cobra.Command, args []string) { util.commonAccountParams.Hostname = args[0] util.deleteAccountParams.AccountID = args[1] res, err := util.deleteAccount(&util.commonAccountParams, &util.deleteAccountParams) printResponse(res, err) } func (util *AccountUtil) deleteAccount(common *CommonAccountParams, params *DeleteAccountParams) (*DeleteAccountResult, error) { var err error res := &DeleteAccountResult{} hostname, err := packUserinfo(common.Hostname, common.Username, common.Password) if err != nil { return res, err } timeout := time.Duration(common.Timeout) * time.Second ctx, _ := context.WithTimeout(context.Background(), timeout) re := regexp.MustCompile(uuidRegex) id := strings.ToLower(params.AccountID) if re.MatchString(id) { err = client.NewClient().DeleteAccountByID(ctx, hostname, id) } else { err = client.NewClient().DeleteAccountByName(ctx, hostname, params.AccountID) } if err != nil { return res, err } return res, err } // ListAccounts type ListAccountsParams struct { Timeout uint64 Detail bool Regex string } type Userinfo struct { Username string `yaml:"username,omitempty"` AccountID string `yaml:"accountId,omitempty"` Rights map[string]string `yaml:"rights,omitempty"` } type ListAccountsResult struct { Accounts []descr.AccountShort `yaml:"accounts,omitempty"` Users []Userinfo `yaml:"users,omitempty"` } func (util *AccountUtil) ListAccounts(cmd *cobra.Command, args []string) { util.commonAccountParams.Hostname = args[0] res, err := util.listAccounts(&util.commonAccountParams, &util.listAccountsParams) printResponse(res, err) } func (util *AccountUtil) listAccounts(common *CommonAccountParams, params *ListAccountsParams) (*ListAccountsResult, error) { var err error res := &ListAccountsResult{} hostname, err := packUserinfo(common.Hostname, common.Username, common.Password) if err != nil { return res, err } outRe, err := regexp.Compile(params.Regex) if err != nil { return res, err } timeout := time.Duration(common.Timeout) * time.Second ctx, _ := context.WithTimeout(context.Background(), timeout) accounts, err := client.NewClient().ListAccounts(ctx, hostname) if err != nil { return res, err } outAccounts := make([]descr.AccountShort, 0) if params.Regex != "" { for _, item := range accounts { if outRe.MatchString(item.Username) { outAccounts = append(outAccounts, item) } } } else { outAccounts = accounts } if params.Detail { res.Accounts = outAccounts } else { res.Users = make([]Userinfo, 0) for _, account := range outAccounts { userinfo := Userinfo{ Username: account.Username, AccountID: account.ID, Rights: make(map[string]string, 0), } for _, grant := range account.Grants { userinfo.Rights[grant.ID] = grant.Right } res.Users = append(res.Users, userinfo) } } return res, err }