github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/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  }