Skip to content

Commit

Permalink
Add Account Deletion and Change Password to CLI Commands (#983)
Browse files Browse the repository at this point in the history
Added functionality allowing users to delete accounts and change passwords
through the CLI to support recent development of admin ChangePassword and
DeleteAccount APIs on the server side.
  • Loading branch information
sigmaith authored and hackerwins committed Sep 5, 2024
1 parent 2f93922 commit dffc44c
Show file tree
Hide file tree
Showing 9 changed files with 316 additions and 13 deletions.
27 changes: 27 additions & 0 deletions admin/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,3 +377,30 @@ func withShardKey[T any](conn *connect.Request[T], keys ...string) *connect.Requ

return conn
}

// DeleteAccount deletes the user's account.
func (c *Client) DeleteAccount(ctx context.Context, username, password string) error {
_, err := c.client.DeleteAccount(ctx, connect.NewRequest(&api.DeleteAccountRequest{
Username: username,
Password: password,
}))
if err != nil {
return err
}

return nil
}

// ChangePassword changes the user's password.
func (c *Client) ChangePassword(ctx context.Context, username, password, newPassword string) error {
_, err := c.client.ChangePassword(ctx, connect.NewRequest(&api.ChangePasswordRequest{
Username: username,
CurrentPassword: password,
NewPassword: newPassword,
}))
if err != nil {
return err
}

return nil
}
4 changes: 2 additions & 2 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -781,11 +781,11 @@ func (c *Client) broadcast(ctx context.Context, doc *document.Document, topic st
func newTLSConfigFromFile(certFile, serverNameOverride string) (*tls.Config, error) {
b, err := os.ReadFile(filepath.Clean(certFile))
if err != nil {
return nil, fmt.Errorf("credentials: failed to read TLS config file %q: %w", certFile, err)
return nil, fmt.Errorf("read TLS config file %q: %w", certFile, err)
}
cp := x509.NewCertPool()
if !cp.AppendCertsFromPEM(b) {
return nil, fmt.Errorf("credentials: failed to append certificates")
return nil, fmt.Errorf("failure to append certs from PEM")
}

return &tls.Config{ServerName: serverNameOverride, RootCAs: cp, MinVersion: tls.VersionTLS12}, nil
Expand Down
2 changes: 1 addition & 1 deletion cmd/yorkie/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ func Preload(_ *cobra.Command, _ []string) error {
}

if err := viper.ReadInConfig(); err != nil {
return fmt.Errorf("failed to read in config: %w", err)
return fmt.Errorf("read in config: %w", err)
}
return nil
}
136 changes: 136 additions & 0 deletions cmd/yorkie/delete_account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright 2024 The Yorkie Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package main

import (
"context"
"fmt"
"time"

"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/yorkie-team/yorkie/admin"
"github.com/yorkie-team/yorkie/cmd/yorkie/config"
)

func deleteAccountCmd() *cobra.Command {
return &cobra.Command{
Use: "delete-account",
Short: "Delete account",
PreRunE: config.Preload,
RunE: func(_ *cobra.Command, args []string) error {
rpcAddr := viper.GetString("rpcAddr")
auth, err := config.LoadAuth(rpcAddr)
if err != nil {
return err
}

if err := readPassword(); err != nil {
return err
}

if confirmation, err := makeConfirmation(); !confirmation || err != nil {
if err != nil {
return err
}
return nil
}

conf, err := config.Load()
if err != nil {
return err
}

if rpcAddr == "" {
rpcAddr = viper.GetString("rpcAddr")
}

if err := deleteAccount(conf, auth, rpcAddr, username, password); err != nil {
fmt.Println("Failed to delete account: ", err)
}

return nil
},
}
}

func makeConfirmation() (bool, error) {
fmt.Println("Warning: This action cannot be undone. Type 'DELETE' to confirm: ")
var confirmation string
if _, err := fmt.Scanln(&confirmation); err != nil {
return false, fmt.Errorf("read confirmation from user: %w", err)
}

if confirmation != "DELETE" {
return false, fmt.Errorf("account deletion aborted")
}

return true, nil
}

func deleteAccount(conf *config.Config, auth config.Auth, rpcAddr, username, password string) error {
cli, err := admin.Dial(rpcAddr, admin.WithToken(auth.Token), admin.WithInsecure(auth.Insecure))
if err != nil {
return err
}
defer func() {
cli.Close()
}()

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

if err := cli.DeleteAccount(ctx, username, password); err != nil {
return fmt.Errorf("delete account: %w", err)
}

delete(conf.Auths, rpcAddr)
if conf.RPCAddr == rpcAddr {
for addr := range conf.Auths {
conf.RPCAddr = addr
break
}
}

if err := config.Save(conf); err != nil {
return err
}

return nil
}

func init() {
cmd := deleteAccountCmd()
cmd.Flags().StringVarP(
&username,
"username",
"u",
"",
"Username",
)
cmd.Flags().StringVarP(
&password,
"password",
"p",
"",
"Password (optional)",
)

_ = cmd.MarkFlagRequired("username")
rootCmd.AddCommand(cmd)
}
29 changes: 25 additions & 4 deletions cmd/yorkie/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ package main

import (
"context"
"fmt"
"os"

"github.com/spf13/cobra"
"golang.org/x/term"

"github.com/yorkie-team/yorkie/admin"
"github.com/yorkie-team/yorkie/cmd/yorkie/config"
Expand All @@ -35,9 +38,13 @@ var (
func newLoginCmd() *cobra.Command {
return &cobra.Command{
Use: "login",
Short: "Log in to Yorkie server",
Short: "Log in to the Yorkie server",
PreRunE: config.Preload,
RunE: func(cmd *cobra.Command, args []string) error {
if err := readPassword(); err != nil {
return err
}

cli, err := admin.Dial(rpcAddr, admin.WithInsecure(insecure))
if err != nil {
return err
Expand Down Expand Up @@ -74,21 +81,35 @@ func newLoginCmd() *cobra.Command {
}
}

// readPassword reads the password from the user.
func readPassword() error {
if password == "" {
fmt.Print("Enter Password: ")
bytePassword, err := term.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return fmt.Errorf("read password: %w", err)
}
password = string(bytePassword)
fmt.Println()
}
return nil
}

func init() {
cmd := newLoginCmd()
cmd.Flags().StringVarP(
&username,
"username",
"u",
"",
"Username (required if password is set)",
"Username",
)
cmd.Flags().StringVarP(
&password,
"password",
"p",
"",
"Password (required if username is set)",
"Password (optional)",
)
cmd.Flags().StringVar(
&rpcAddr,
Expand All @@ -102,6 +123,6 @@ func init() {
false,
"Skip the TLS connection of the client",
)
cmd.MarkFlagsRequiredTogether("username", "password")
_ = cmd.MarkFlagRequired("username")
rootCmd.AddCommand(cmd)
}
116 changes: 116 additions & 0 deletions cmd/yorkie/passwd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Copyright 2024 The Yorkie Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package main

import (
"context"
"fmt"
"os"

"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/term"

"github.com/yorkie-team/yorkie/admin"
"github.com/yorkie-team/yorkie/cmd/yorkie/config"
)

func passwdCmd() *cobra.Command {
return &cobra.Command{
Use: "passwd",
Short: "Change password",
PreRunE: config.Preload,
RunE: func(cmd *cobra.Command, args []string) error {
rpcAddr := viper.GetString("rpcAddr")
auth, err := config.LoadAuth(rpcAddr)
if err != nil {
return err
}

password, newPassword, err := readPasswords()
if err != nil {
return err
}

cli, err := admin.Dial(rpcAddr, admin.WithToken(auth.Token), admin.WithInsecure(auth.Insecure))
if err != nil {
return err
}
defer func() {
cli.Close()
}()

ctx := context.Background()
if err := cli.ChangePassword(ctx, username, password, newPassword); err != nil {
return err
}

if err := deleteAuthSession(rpcAddr); err != nil {
return err
}

return nil
},
}
}

func readPasswords() (string, string, error) {
fmt.Print("Enter Password: ")
bytePassword, err := term.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return "", "", fmt.Errorf("read password: %w", err)
}
password := string(bytePassword)
fmt.Println()

fmt.Print("Enter New Password: ")
bytePassword, err = term.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return "", "", fmt.Errorf("read new password: %w", err)
}
newPassword := string(bytePassword)
fmt.Println()

return password, newPassword, nil
}

func deleteAuthSession(rpcAddr string) error {
conf, err := config.Load()
if err != nil {
return err
}

delete(conf.Auths, rpcAddr)
if err := config.Save(conf); err != nil {
return err
}

return nil
}

func init() {
cmd := passwdCmd()
cmd.Flags().StringVarP(
&username,
"username",
"u",
"",
"Username",
)
_ = cmd.MarkFlagRequired("username")
rootCmd.AddCommand(cmd)
}
4 changes: 2 additions & 2 deletions cmd/yorkie/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,13 @@ func printVersionInfo(cmd *cobra.Command, output string, versionInfo *types.Vers
case "yaml":
marshalled, err := yaml.Marshal(versionInfo)
if err != nil {
return errors.New("failed to marshal YAML")
return errors.New("marshal YAML")
}
cmd.Println(string(marshalled))
case "json":
marshalled, err := json.MarshalIndent(versionInfo, "", " ")
if err != nil {
return errors.New("failed to marshal JSON")
return errors.New("marshal JSON")
}
cmd.Println(string(marshalled))
default:
Expand Down
Loading

0 comments on commit dffc44c

Please sign in to comment.