package config import ( "bufio" "encoding/json" "errors" "fmt" "io" "net" "os" "path" "golang.org/x/crypto/bcrypt" ) var ( // File to store the app configuration, like username, password, token, repo dir, logging level ConfigFilePath string // Repo dir NpmRepoDir string // HTTPListen HTTPListen string // Set Verbose Logging CanLog bool // Logging Level LoggingLvl string // Username RegUser string // Password RegPwd string ) type Config struct { Token string `json:"token"` RepoDir string `json:"repoDir"` IpAddress string `json:"ipAddress"` LogLevel string `json:"logLevel"` } func checkIfCorrectIPPort(s string) bool { host, port, _ := net.SplitHostPort(s) if host == "" || port == "" { return false } if net.ParseIP(host) != nil { return true } _, err := net.ResolveIPAddr("ip", host) return err == nil } func VerifyConfig() error { if !checkIfCorrectIPPort(HTTPListen) { return errors.New("ip address should be in the format of :") } // Check if config file exists dirname, err := os.UserHomeDir() if err != nil { return err } configDirPath, err := checkOrCreateConfigDir(dirname, true) if err != nil { return err } // TODO: Create a logging file (it should be located at .gosimplenpm/applogs) ConfigFilePath = path.Join(configDirPath, "config.json") if NpmRepoDir == "" { NpmRepoDir = path.Join(dirname, ".gosimplenpm", "registry") err := checkOrCreateRepoDir(NpmRepoDir) if err != nil { return err } } else if NpmRepoDir != "" { err := checkOrCreateRepoDir(NpmRepoDir) if err != nil { return err } } if CanLog { LoggingLvl = "DEBUG" fmt.Println("\n Enabled debug logging") } else { LoggingLvl = "INFO" } return nil } func checkOrCreateConfigDir(fp string, canCreate bool) (string, error) { configDirPath := path.Join(fp, ".gosimplenpm", "config") ok := isDir(configDirPath) if !ok && canCreate { err := os.MkdirAll(configDirPath, os.ModePerm) if err != nil { return "", err } } if !ok && !canCreate { return "", nil } return configDirPath, nil } func checkOrCreateRepoDir(repoDirPath string) error { ok := isDir(repoDirPath) if !ok { err := os.MkdirAll(repoDirPath, os.ModePerm) if err != nil { return err } } return nil } func createConfig(cfg *Config, recreate bool) error { var scanner *bufio.Scanner if recreate { fmt.Println("\nNew config variables. Saving...") } else { fmt.Println("\nConfig file is not found. Creating...") } configFile, err := os.Create(ConfigFilePath) if err != nil { return err } defer configFile.Close() cfg.IpAddress = HTTPListen cfg.LogLevel = LoggingLvl // Get username if cfg.Token == "" { fmt.Println("Enter your username: ") scanner = bufio.NewScanner(os.Stdin) scanner.Scan() err = scanner.Err() if err != nil { return err } RegUser = scanner.Text() fmt.Println("Enter your password: ") scanner = bufio.NewScanner(os.Stdin) scanner.Scan() err = scanner.Err() if err != nil { return err } RegPwd = scanner.Text() token, err := generateAuthToken() if err != nil { return err } cfg.Token = token } fmt.Printf("The npm authToken is %s.\n", cfg.Token) err = json.NewEncoder(configFile).Encode(cfg) if err != nil { return err } return nil } func loadConfig(cfg *Config) error { configFile, err := os.Open(ConfigFilePath) if err != nil { return err } defer configFile.Close() err = json.NewDecoder(configFile).Decode(cfg) if err != nil { return err } return nil } func LoadOrCreateConfig(cfg *Config) error { var err error ok := isFile(ConfigFilePath) // If file is not found if !ok { err = createConfig(cfg, false) if err != nil { return err } } if ok { // File is found err = loadConfig(cfg) if err != nil { return err } if cfg.Token == "" || cfg.IpAddress != HTTPListen || cfg.LogLevel != LoggingLvl || cfg.RepoDir != NpmRepoDir { // recreate the config file err = createConfig(cfg, true) if err != nil { return err } } } return err } func isFile(fp string) bool { info, err := os.Stat(fp) if os.IsNotExist(err) || !info.Mode().IsRegular() { return false } return true } func isDir(fp string) bool { info, err := os.Stat(fp) if os.IsNotExist(err) || !info.IsDir() { return false } return true } // Hash password func hashPassword(password string) (string, error) { // Convert password string to byte slice var passwordBytes = []byte(password) // Hash password with Bcrypt's min cost hashedPasswordBytes, err := bcrypt.GenerateFromPassword(passwordBytes, bcrypt.MinCost) return string(hashedPasswordBytes), err } // Check if two passwords match using Bcrypt's CompareHashAndPassword // which return nil on success and an error on failure. // func doPasswordsMatch(hashedPassword, currPassword string) bool { // err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(currPassword)) // return err == nil // } func generateAuthToken() (string, error) { hashed, err := hashPassword(RegPwd) if err != nil { return "", err } token := fmt.Sprintf("%s::%s", RegUser, hashed) return token, nil } func PrintConfigFile() error { // Check if config file exists dirname, err := os.UserHomeDir() if err != nil { return err } configDirPath, err := checkOrCreateConfigDir(dirname, false) if err != nil { return err } if configDirPath == "" { return errors.New("config dir is not found") } ConfigFilePath = path.Join(configDirPath, "config.json") ok := isFile(ConfigFilePath) if !ok { return errors.New("config file is not found") } configFile, err := os.Open(ConfigFilePath) if err != nil { return err } defer configFile.Close() b, err := io.ReadAll(configFile) if err != nil { return err } var result map[string]interface{} err = json.Unmarshal([]byte(b), &result) if err != nil { return err } // Pretty-print the result marshaled, err := json.MarshalIndent(result, "", " ") if err != nil { return err } fmt.Printf("Printing config located at %s: \n %s\n", ConfigFilePath, (marshaled)) return nil }