github.com/nuvolaris/nuv@v0.0.0-20240511174247-a74e3a52bfd8/auth/login.go (about)

     1  // Licensed to the Apache Software Foundation (ASF) under one
     2  // or more contributor license agreements.  See the NOTICE file
     3  // distributed with this work for additional information
     4  // regarding copyright ownership.  The ASF licenses this file
     5  // to you under the Apache License, Version 2.0 (the
     6  // "License"); you may not use this file except in compliance
     7  // with the License.  You may obtain a copy of the License at
     8  //
     9  //   http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing,
    12  // software distributed under the License is distributed on an
    13  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    14  // KIND, either express or implied.  See the License for the
    15  // specific language governing permissions and limitations
    16  // under the License.
    17  
    18  package auth
    19  
    20  import (
    21  	"bytes"
    22  	"encoding/json"
    23  	"errors"
    24  	"flag"
    25  	"fmt"
    26  	"io"
    27  	"log"
    28  	"net/http"
    29  	"os"
    30  	"path/filepath"
    31  
    32  	"github.com/mitchellh/go-homedir"
    33  	"github.com/nuvolaris/nuv/config"
    34  	"github.com/zalando/go-keyring"
    35  )
    36  
    37  type LoginResult struct {
    38  	Login   string
    39  	Auth    string
    40  	ApiHost string
    41  }
    42  
    43  const usage = `Usage:
    44  nuv login <apihost> [<user>]
    45  
    46  Login to a Nuvolaris instance. If no user is specified, the default user "nuvolaris" is used.
    47  You can use the environment variables NUV_APIHOST and NUV_USER to avoid specifying them on the command line.
    48  And NUV_PASSWORD to avoid entering the password interactively.
    49  
    50  Options:
    51    -h, --help   Show usage`
    52  
    53  const whiskLoginPath = "/api/v1/web/whisk-system/nuv/login"
    54  const defaultUser = "nuvolaris"
    55  const nuvSecretServiceName = "nuvolaris"
    56  
    57  func LoginCmd() (*LoginResult, error) {
    58  	flag := flag.NewFlagSet("login", flag.ExitOnError)
    59  	flag.Usage = func() {
    60  		fmt.Println(usage)
    61  	}
    62  
    63  	var helpFlag bool
    64  	flag.BoolVar(&helpFlag, "h", false, "Show usage")
    65  	flag.BoolVar(&helpFlag, "help", false, "Show usage")
    66  	err := flag.Parse(os.Args[1:])
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  
    71  	if helpFlag {
    72  		flag.Usage()
    73  		return nil, nil
    74  	}
    75  
    76  	args := flag.Args()
    77  
    78  	if len(args) == 0 && os.Getenv("NUV_APIHOST") == "" {
    79  		flag.Usage()
    80  		return nil, errors.New("missing apihost")
    81  	}
    82  
    83  	password := os.Getenv("NUV_PASSWORD")
    84  	if password == "" {
    85  		fmt.Print("Enter Password: ")
    86  		pwd, err := AskPassword()
    87  		if err != nil {
    88  			return nil, err
    89  		}
    90  		password = pwd
    91  		fmt.Println()
    92  	}
    93  
    94  	apihost := os.Getenv("NUV_APIHOST")
    95  	if apihost == "" {
    96  		apihost = args[0]
    97  	}
    98  	url := apihost + whiskLoginPath
    99  
   100  	// try to get the user from the environment
   101  	user := os.Getenv("NUV_USER")
   102  	if user == "" {
   103  		// if env var not set, try to get it from the command line
   104  		if os.Getenv("NUV_APIHOST") != "" {
   105  			// if apihost env var was set, treat the first arg as the user
   106  			if len(args) > 0 {
   107  				user = args[0]
   108  			}
   109  		} else {
   110  			// if apihost env var was not set, treat the second arg as the user
   111  			if len(args) > 1 {
   112  				user = args[1]
   113  			} 
   114  		}
   115  	}
   116  
   117  	// if still not set, use the default user
   118  	if user == "" {
   119  		log.Println("Using the default user:", defaultUser)
   120  		user = defaultUser
   121  	}
   122  
   123  	log.Println("Logging in as", user, "to", apihost)
   124  
   125  	creds, err := doLogin(url, user, password)
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  
   130  	if _, ok := creds["AUTH"]; !ok {
   131  		return nil, errors.New("missing AUTH token from login response")
   132  	}
   133  
   134  	nuvHome, err := homedir.Expand("~/.nuv")
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	configMap, err := config.NewConfigMapBuilder().
   140  		WithConfigJson(filepath.Join(nuvHome, "config.json")).
   141  		Build()
   142  
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  
   147  	for k, v := range creds {
   148  		if err := configMap.Insert(k, v); err != nil {
   149  			return nil, err
   150  		}
   151  	}
   152  
   153  	err = configMap.SaveConfig()
   154  	if err != nil {
   155  		return nil, err
   156  	}
   157  
   158  	// if err := storeCredentials(creds); err != nil {
   159  	// 	return nil, err
   160  	// }
   161  
   162  	// auth, err := keyring.Get(nuvSecretServiceName, "AUTH")
   163  	// if err != nil {
   164  	// 	return nil, err
   165  	// }
   166  
   167  	return &LoginResult{
   168  		Login:   user,
   169  		Auth:    creds["AUTH"],
   170  		ApiHost: apihost,
   171  	}, nil
   172  }
   173  
   174  func doLogin(url, user, password string) (map[string]string, error) {
   175  	data := map[string]string{
   176  		"login":    user,
   177  		"password": password,
   178  	}
   179  	loginJson, err := json.Marshal(data)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  
   184  	resp, err := http.Post(url, "application/json", bytes.NewBuffer(loginJson))
   185  	if err != nil {
   186  		return nil, err
   187  	}
   188  	defer resp.Body.Close()
   189  
   190  	if resp.StatusCode != http.StatusOK {
   191  		body, err := io.ReadAll(resp.Body)
   192  		if err != nil {
   193  			return nil, fmt.Errorf("login failed with status code %d", resp.StatusCode)
   194  		}
   195  		return nil, fmt.Errorf("login failed (%d): %s", resp.StatusCode, string(body))
   196  	}
   197  
   198  	var creds map[string]string
   199  	err = json.NewDecoder(resp.Body).Decode(&creds)
   200  	if err != nil {
   201  		return nil, errors.New("failed to decode response from login request")
   202  	}
   203  
   204  	return creds, nil
   205  }
   206  
   207  func storeCredentials(creds map[string]string) error {
   208  	for k, v := range creds {
   209  		err := keyring.Set(nuvSecretServiceName, k, v)
   210  		if err != nil {
   211  			return err
   212  		}
   213  	}
   214  
   215  	return nil
   216  }