/* * 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/app/descr" "mstore/pkg/client" ) 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 operation", Aliases: []string{"account"}, } const defaultTimeout uint64 = 10 subCmd.PersistentFlags().StringVarP(&util.commonAccountParams.Username, "user", "u", "", "Username") subCmd.PersistentFlags().StringVarP(&util.commonAccountParams.Password, "pass", "p", "", "Password") subCmd.PersistentFlags().StringVarP(&util.commonAccountParams.Hostname, "host", "x", defaultHostname, "Hostname") subCmd.PersistentFlags().Uint64VarP(&util.commonAccountParams.Timeout, "timeout", "t", defaultTimeout, "Operation timeout") subCmd.MarkPersistentFlagRequired("host") subCmd.MarkFlagsRequiredTogether("user", "pass") // CreateAccount var createAccountCmd = &cobra.Command{ Use: "create", Short: "Create user account", Run: util.CreateAccount, } createAccountCmd.Flags().StringVarP(&util.createAccountParams.NewUsername, "newuser", "U", "", "New account username") createAccountCmd.Flags().StringVarP(&util.createAccountParams.NewPassword, "newpass", "P", "", "New account password") createAccountCmd.MarkFlagsRequiredTogether("newuser", "newpass") subCmd.AddCommand(createAccountCmd) // GetAccount var getAccountCmd = &cobra.Command{ Use: "get", Short: "Get account info", Run: util.GetAccount, } getAccountCmd.Flags().StringVarP(&util.getAccountParams.AccountID, "id", "I", "", "Account ID or name") getAccountCmd.Flags().StringVarP(&util.getAccountParams.AccountID, "name", "n", "", "Account ID or name") getAccountCmd.MarkFlagsOneRequired("id", "name") subCmd.AddCommand(getAccountCmd) // UpdateAccount var updateAccountCmd = &cobra.Command{ Use: "update", Short: "Update account parameters", Run: util.UpdateAccount, } updateAccountCmd.Flags().StringVarP(&util.updateAccountParams.AccountID, "id", "I", "", "Account ID or username") updateAccountCmd.Flags().StringVarP(&util.updateAccountParams.AccountID, "name", "n", "", "Account ID or username") updateAccountCmd.Flags().StringVarP(&util.updateAccountParams.NewUsername, "newname", "N", "", "New username") updateAccountCmd.Flags().StringVarP(&util.updateAccountParams.NewPassword, "newpass", "P", "", "New password") updateAccountCmd.MarkFlagsOneRequired("id", "name") updateAccountCmd.MarkFlagsOneRequired("newname", "newpass") subCmd.AddCommand(updateAccountCmd) // DeleteAccount var deleteAccountCmd = &cobra.Command{ Use: "delete", Short: "Delete account", Run: util.DeleteAccount, } deleteAccountCmd.Flags().StringVarP(&util.deleteAccountParams.AccountID, "id", "I", "", "Account ID") deleteAccountCmd.Flags().StringVarP(&util.updateAccountParams.AccountID, "name", "n", "", "Account ID or username") deleteAccountCmd.MarkFlagsOneRequired("id", "name") subCmd.AddCommand(deleteAccountCmd) // ListAccount var listAccountsCmd = &cobra.Command{ Use: "list", Short: "list accounts", 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") listAccountsCmd.MarkFlagRequired("host") subCmd.AddCommand(listAccountsCmd) viper.BindPFlags(subCmd.Flags()) viper.SetEnvPrefix("MSTORE") // Bind environment variables viper.BindEnv("user") viper.BindEnv("pass") viper.BindEnv("host") return subCmd } // CreateAccount type CreateAccountParams struct { NewUsername string NewPassword string } type CreateAccountResult struct { AccountID string `yaml:"accountId"` GrantIDs []string `yaml:"grantsIds,omitempty"` } func (util *AccountUtil) CreateAccount(cmd *cobra.Command, args []string) { 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{ GrantIDs: make([]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{ descr.RightWriteAccounts, descr.RightReadAccounts, descr.RightWriteFiles, descr.RightReadFiles, descr.RightWriteImages, descr.RightReadImages, } for _, right := range fullRights { id, err := client.NewClient().CreateGrant(ctx, hostname, accountID, right, ".*") if err != nil { return res, err } res.GrantIDs = append(res.GrantIDs, id) } res.AccountID = accountID return res, err } // UpdateAccount type UpdateAccountParams struct { Hostname string Username string Password string 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) { 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 { Hostname string Username string Password string Timeout uint64 AccountID string } func (util *AccountUtil) GetAccount(cmd *cobra.Command, args []string) { 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 { Hostname string Username string Password string AccountID string Timeout uint64 } type DeleteAccountResult struct{} func (util *AccountUtil) DeleteAccount(cmd *cobra.Command, args []string) { 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 { Hostname string Username string Password string Timeout uint64 Detail bool Regex string } type Userinfo struct { Username string `yaml:"username,omitempty"` Rights []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) { 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, Rights: make([]string, 0), } for _, grant := range account.Grants { userinfo.Rights = append(userinfo.Rights, grant.Right) } res.Users = append(res.Users, userinfo) } } return res, err }