github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/kbpagesconfig/common.go (about) 1 // Copyright 2018 Keybase Inc. All rights reserved. 2 // Use of this source code is governed by a BSD 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "bytes" 9 "crypto/rand" 10 "encoding/base64" 11 "errors" 12 "fmt" 13 "io" 14 "os" 15 "path/filepath" 16 "strings" 17 18 "github.com/keybase/client/go/kbfs/libpages/config" 19 "github.com/sergi/go-diff/diffmatchpatch" 20 ) 21 22 type prompter interface { 23 Prompt(string) (string, error) 24 PromptPassword(string) (string, error) 25 } 26 27 // Not go-routine safe! 28 type kbpConfigEditor struct { 29 kbpConfigPath string 30 kbpConfig *config.V1 31 originalConfigStr string 32 prompter prompter 33 } 34 35 func readConfigAndClose(from io.ReadCloser) ( 36 cfg config.Config, str string, err error) { 37 defer from.Close() 38 buf := &bytes.Buffer{} 39 if cfg, err = config.ParseConfig(io.TeeReader(from, buf)); err != nil { 40 return nil, "", err 41 } 42 return cfg, buf.String(), nil 43 } 44 45 func kbpConfigPath(kbpConfigDir string) (path string, err error) { 46 fi, err := os.Stat(kbpConfigDir) 47 if err != nil { 48 return "", fmt.Errorf("stat %q error: %v", kbpConfigDir, err) 49 } 50 if !fi.IsDir() { 51 return "", fmt.Errorf("%q is not a directory", kbpConfigDir) 52 } 53 return filepath.Join(kbpConfigDir, config.DefaultConfigFilename), nil 54 } 55 56 func promptConfirm(p prompter, text string, defaultYes bool) (confirmed bool, err error) { 57 prompt := "(y/N)" 58 if defaultYes { 59 prompt = "(Y/n)" 60 } 61 input, err := p.Prompt(fmt.Sprintf("%s %s: ", text, prompt)) 62 if err != nil { 63 return false, fmt.Errorf("getting confirmation error: %v", err) 64 } 65 switch strings.ToLower(input) { 66 case "y": 67 return true, nil 68 case "n": 69 return false, nil 70 case "": 71 return defaultYes, nil 72 default: 73 return false, errors.New("getting confirmation error") 74 } 75 } 76 77 func confirmAndWrite( 78 originalConfigStr string, 79 newConfig config.Config, 80 configPath string, 81 p prompter) (err error) { 82 buf := &bytes.Buffer{} 83 if err := newConfig.Encode(buf, true); err != nil { 84 return fmt.Errorf("encoding config error: %v", err) 85 } 86 newConfigStr := buf.String() 87 if newConfigStr == originalConfigStr { 88 fmt.Println("no change is made to the config") 89 return nil 90 } 91 92 // print the diff 93 d := diffmatchpatch.New() 94 fmt.Println( 95 d.DiffPrettyText( 96 d.DiffMain(originalConfigStr, newConfigStr, true))) 97 98 confirmed, err := promptConfirm(p, fmt.Sprintf( 99 "confirm writing above changes to %s?", configPath), false) 100 if err != nil { 101 return err 102 } 103 if !confirmed { 104 return fmt.Errorf("not confirmed") 105 } 106 107 // Write the new config to kbpConfigPath. 108 f, err := os.Create(configPath) 109 if err != nil { 110 return fmt.Errorf( 111 "opening file [%s] error: %v", configPath, err) 112 } 113 defer f.Close() 114 if _, err = f.WriteString(newConfigStr); err != nil { 115 116 return fmt.Errorf( 117 "writing config to file [%s] error: %v", configPath, err) 118 } 119 if err = f.Close(); err != nil { 120 return fmt.Errorf( 121 "closing file [%s] error: %v", configPath, err) 122 } 123 124 return nil 125 } 126 127 const randomPasswordBytes = 12 // 96 bits entropy 128 func generateRandomPassword() (password string, err error) { 129 bytes := make([]byte, randomPasswordBytes) 130 n, err := rand.Read(bytes) 131 if err != nil { 132 return "", fmt.Errorf("reading random bytes error: %v", err) 133 } 134 if n != randomPasswordBytes { 135 return "", errors.New("reading random bytes error") 136 } 137 return base64.RawURLEncoding.EncodeToString(bytes), nil 138 }