auth/login.go (170 lines of code) (raw):
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package auth
import (
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/apache/openserverless-cli/config"
"github.com/zalando/go-keyring"
)
type LoginResult struct {
Login string
Auth string
ApiHost string
}
const usage = `Usage:
ops -login <apihost> [<user>]
Login to an OpenServerless instance. If no user is specified, the default user "nuvolaris" is used.
You can set the environment variables OPS_APIHOST and OPS_USER to avoid specifying them on the command line.
You can set OPS_PASSWORD to avoid entering the password interactively.
Options:
-h, --help Show usage`
const whiskLoginPath = "/api/v1/web/whisk-system/nuv/login"
const defaultUser = "nuvolaris"
const opsSecretServiceName = "nuvolaris"
func LoginCmd() (*LoginResult, error) {
// enable log output if requested
if os.Getenv("DEBUG")+os.Getenv("TRACE") != "" {
log.SetOutput(os.Stdout)
}
flag := flag.NewFlagSet("-login", flag.ExitOnError)
flag.Usage = func() {
fmt.Println(usage)
}
var helpFlag bool
flag.BoolVar(&helpFlag, "h", false, "Show usage")
flag.BoolVar(&helpFlag, "help", false, "Show usage")
err := flag.Parse(os.Args[1:])
if err != nil {
return nil, err
}
if helpFlag {
flag.Usage()
return nil, nil
}
args := flag.Args()
if len(args) == 0 && os.Getenv("OPS_APIHOST") == "" {
flag.Usage()
return nil, errors.New("missing apihost")
}
apihost := os.Getenv("OPS_APIHOST")
if apihost == "" {
apihost = args[0]
}
url := apihost + whiskLoginPath
apihost = ensureSchema(apihost)
// try to get the user from the environment
user := os.Getenv("OPS_USER")
if user == "" {
// if env var not set, try to get it from the command line
if os.Getenv("OPS_APIHOST") != "" {
// if apihost env var was set, treat the first arg as the user
if len(args) > 0 {
user = args[0]
}
} else {
// if apihost env var was not set, treat the second arg as the user
if len(args) > 1 {
user = args[1]
}
}
}
// if still not set, use the default user
if user == "" {
fmt.Println("Using the default user:", defaultUser)
user = defaultUser
}
fmt.Println("Logging in", apihost, "as", user)
password := os.Getenv("OPS_PASSWORD")
if password == "" {
fmt.Print("Enter Password: ")
pwd, err := AskPassword()
if err != nil {
return nil, err
}
password = pwd
fmt.Println()
}
creds, err := doLogin(url, user, password)
if err != nil {
return nil, err
}
if _, ok := creds["AUTH"]; !ok {
return nil, errors.New("missing AUTH token from login response")
}
opsHome := os.Getenv("OPS_HOME")
if opsHome == "" {
return nil, fmt.Errorf("OPS_HOME not defined")
}
configMap, err := config.NewConfigMapBuilder().
WithConfigJson(filepath.Join(opsHome, "config.json")).
Build()
if err != nil {
return nil, err
}
for k, v := range creds {
if err := configMap.Insert(k, v); err != nil {
return nil, err
}
}
if err := configMap.Insert("STATUS_LOGGED_USER", user); err != nil {
log.Println("[Warning] Failed to insert STATUS_LOGGED_USER")
}
err = configMap.SaveConfig()
if err != nil {
return nil, err
}
// if err := storeCredentials(creds); err != nil {
// return nil, err
// }
// auth, err := keyring.Get(opsSecretServiceName, "AUTH")
// if err != nil {
// return nil, err
// }
return &LoginResult{
Login: user,
Auth: creds["AUTH"],
ApiHost: apihost,
}, nil
}
func ensureSchema(apihost string) string {
if !strings.HasPrefix(apihost, "http://") && !strings.HasPrefix(apihost, "https://") {
if apihost == "localhost" {
apihost = "http://" + apihost
} else {
apihost = "https://" + apihost
}
}
return apihost
}
func doLogin(url, user, password string) (map[string]string, error) {
data := map[string]string{
"login": user,
"password": password,
}
loginJson, err := json.Marshal(data)
if err != nil {
return nil, err
}
resp, err := http.Post(url, "application/json", bytes.NewBuffer(loginJson))
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("login failed with status code %d", resp.StatusCode)
}
return nil, fmt.Errorf("login failed (%d): %s", resp.StatusCode, string(body))
}
var creds map[string]string
err = json.NewDecoder(resp.Body).Decode(&creds)
if err != nil {
return nil, errors.New("failed to decode response from login request")
}
return creds, nil
}
func storeCredentials(creds map[string]string) error {
for k, v := range creds {
err := keyring.Set(opsSecretServiceName, k, v)
if err != nil {
return err
}
}
return nil
}