/ internal / config / credentials.go
credentials.go
  1  package config
  2  
  3  import (
  4  	"encoding/json"
  5  	"errors"
  6  	"fmt"
  7  	"os"
  8  	"path/filepath"
  9  
 10  	"codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
 11  )
 12  
 13  const (
 14  	defaultCredentialsFileName = "credentials.json"
 15  )
 16  
 17  type CredentialsConfig struct {
 18  	CurrentAccount string                 `json:"currentAccount"`
 19  	Credentials    map[string]Credentials `json:"credentials"`
 20  }
 21  
 22  type Credentials struct {
 23  	Instance     string `json:"instance"`
 24  	ClientID     string `json:"clientId"`
 25  	ClientSecret string `json:"clientSecret"`
 26  	AccessToken  string `json:"accessToken"`
 27  }
 28  
 29  type CredentialsNotFoundError struct {
 30  	AccountName string
 31  }
 32  
 33  func (e CredentialsNotFoundError) Error() string {
 34  	return "unable to find the credentials for the account '" + e.AccountName + "'"
 35  }
 36  
 37  // SaveCredentials saves the credentials into the credentials file within the specified configuration
 38  // directory. If the directory is not specified then the default directory is used. If the directory
 39  // is not present, it will be created.
 40  func SaveCredentials(filePath, username string, credentials Credentials) (string, error) {
 41  	part := filepath.Dir(filePath)
 42  
 43  	// ensure that the directory exists.
 44  	credentialsDir, err := utilities.CalculateConfigDir(part)
 45  	if err != nil {
 46  		return "", fmt.Errorf("unable to calculate the directory to your credentials file: %w", err)
 47  	}
 48  
 49  	if err := utilities.EnsureDirectory(credentialsDir); err != nil {
 50  		return "", fmt.Errorf("unable to ensure the configuration directory: %w", err)
 51  	}
 52  
 53  	var authConfig CredentialsConfig
 54  
 55  	if _, err := os.Stat(filePath); err != nil {
 56  		if !errors.Is(err, os.ErrNotExist) {
 57  			return "", fmt.Errorf("unknown error received when running stat on %s: %w", filePath, err)
 58  		}
 59  
 60  		authConfig.Credentials = make(map[string]Credentials)
 61  	} else {
 62  		authConfig, err = NewCredentialsConfigFromFile(filePath)
 63  		if err != nil {
 64  			return "", fmt.Errorf("unable to retrieve the existing authentication configuration: %w", err)
 65  		}
 66  	}
 67  
 68  	instance := utilities.GetFQDN(credentials.Instance)
 69  
 70  	authenticationName := username + "@" + instance
 71  
 72  	authConfig.CurrentAccount = authenticationName
 73  
 74  	authConfig.Credentials[authenticationName] = credentials
 75  
 76  	if err := saveCredentialsConfigFile(authConfig, filePath); err != nil {
 77  		return "", fmt.Errorf("unable to save the authentication configuration to file: %w", err)
 78  	}
 79  
 80  	return authenticationName, nil
 81  }
 82  
 83  // UpdateCurrentAccount updates the name of the current account in the credentials config file.
 84  func UpdateCurrentAccount(account string, filePath string) error {
 85  	credentialsConfig, err := NewCredentialsConfigFromFile(filePath)
 86  	if err != nil {
 87  		return fmt.Errorf("unable to retrieve the existing authentication configuration: %w", err)
 88  	}
 89  
 90  	if _, ok := credentialsConfig.Credentials[account]; !ok {
 91  		return CredentialsNotFoundError{account}
 92  	}
 93  
 94  	credentialsConfig.CurrentAccount = account
 95  
 96  	if err := saveCredentialsConfigFile(credentialsConfig, filePath); err != nil {
 97  		return fmt.Errorf("unable to save the authentication configuration to file: %w", err)
 98  	}
 99  
100  	return nil
101  }
102  
103  // NewCredentialsConfigFromFile creates a new CredentialsConfig value from reading
104  // the credentials file.
105  func NewCredentialsConfigFromFile(path string) (CredentialsConfig, error) {
106  	file, err := utilities.OpenFile(path)
107  	if err != nil {
108  		return CredentialsConfig{}, fmt.Errorf("unable to open %s: %w", path, err)
109  	}
110  	defer file.Close()
111  
112  	var authConfig CredentialsConfig
113  
114  	if err := json.NewDecoder(file).Decode(&authConfig); err != nil {
115  		return CredentialsConfig{}, fmt.Errorf("unable to decode the JSON data: %w", err)
116  	}
117  
118  	return authConfig, nil
119  }
120  
121  func saveCredentialsConfigFile(authConfig CredentialsConfig, filePath string) error {
122  	file, err := utilities.CreateFile(filePath)
123  	if err != nil {
124  		return fmt.Errorf("unable to create the file at %s: %w", filePath, err)
125  	}
126  	defer file.Close()
127  
128  	encoder := json.NewEncoder(file)
129  	encoder.SetIndent("", "    ")
130  
131  	if err := encoder.Encode(authConfig); err != nil {
132  		return fmt.Errorf("unable to save the JSON data to the authentication config file: %w", err)
133  	}
134  
135  	return nil
136  }
137  
138  func defaultCredentialsConfigFile(configDir string) (string, error) {
139  	dir, err := utilities.CalculateConfigDir(configDir)
140  	if err != nil {
141  		return "", fmt.Errorf("unable to calculate the config directory: %w", err)
142  	}
143  
144  	path, err := utilities.AbsolutePath(filepath.Join(dir, defaultCredentialsFileName))
145  	if err != nil {
146  		return "", fmt.Errorf("unable to get the absolute path to the credentials config file: %w", err)
147  	}
148  
149  	return path, nil
150  }