Skip to content

feat: Login via CLI #298

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Feb 18, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add login prompt for non-first-time-user case
  • Loading branch information
bryphe-coder committed Feb 16, 2022
commit 9ec938ec7bd58d4c50f9d05955c44c5be341bed8
104 changes: 96 additions & 8 deletions cli/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,30 @@ package cli

import (
"fmt"
"io/ioutil"
"net/url"
"os/exec"
"os/user"
"runtime"
"strings"

"github.com/fatih/color"
"github.com/go-playground/validator/v10"
"github.com/manifoldco/promptui"
"github.com/pkg/browser"
"github.com/spf13/cobra"
"golang.org/x/xerrors"

"github.com/coder/coder/coderd"
"github.com/coder/coder/codersdk"
)

const (
goosWindows = "windows"
goosLinux = "linux"
goosDarwin = "darwin"
)

func login() *cobra.Command {
return &cobra.Command{
Use: "login <url>",
Expand Down Expand Up @@ -116,21 +126,99 @@ func login() *cobra.Command {
if err != nil {
return xerrors.Errorf("login with password: %w", err)
}
config := createConfig(cmd)
err = config.Session().Write(resp.SessionToken)
if err != nil {
return xerrors.Errorf("write session token: %w", err)
}
err = config.URL().Write(serverURL.String())

err = saveSessionToken(cmd, client, resp.SessionToken, serverURL)
if err != nil {
return xerrors.Errorf("write server url: %w", err)
return xerrors.Errorf("save session token: %w", err)
}

_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s Welcome to Coder, %s! You're authenticated.\n", color.HiBlackString(">"), color.HiCyanString(username))
return nil
}

authURL := *serverURL
authURL.Path = serverURL.Path + "/cli-auth"
if err := openURL(authURL.String()); err != nil {
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Open the following in your browser:\n\n\t%s\n\n", authURL.String())
} else {
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Your browser has been opened to visit:\n\n\t%s\n\n", authURL.String())
}

apiKey, err := prompt(cmd, &promptui.Prompt{
Label: "Paste your token here:",
Validate: func(token string) error {
client.SessionToken = token
_, err := client.User(cmd.Context(), "me")
if err != nil {
return xerrors.New("That's not a valid token!")
}
return err
},
})
if err != nil {
return xerrors.Errorf("specify email prompt: %w", err)
}

err = saveSessionToken(cmd, client, apiKey, serverURL)
if err != nil {
return xerrors.Errorf("save session token after login: %w", err)
}

return nil
},
}
}

func saveSessionToken(cmd *cobra.Command, client *codersdk.Client, sessionToken string, serverURL *url.URL) error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm hesitant to rip this into a distinct function. We already validate the "me" user request above, so feels like that should be reused.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alternative is to duplicate the persistence code both in the 'create initial user' and 'login' flows - inlined this function in 13696a7

Another thing we could consider is bringing the "me" check above (since it's only required for initial login), and factor out the common save-session-token-and-url logic into this function. Happy with whichever you prefer.

// Login to get user data - verify it is OK before persisting
client.SessionToken = sessionToken
resp, err := client.User(cmd.Context(), "me")
if err != nil {
return xerrors.Errorf("get user: ", err)
}

config := createConfig(cmd)
err = config.Session().Write(sessionToken)
if err != nil {
return xerrors.Errorf("write session token: %w", err)
}
err = config.URL().Write(serverURL.String())
if err != nil {
return xerrors.Errorf("write server url: %w", err)
}

_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s Welcome to Coder, %s! You're authenticated.\n", color.HiBlackString(">"), color.HiCyanString(resp.Username))
return nil
}

// isWSL determines if coder-cli is running within Windows Subsystem for Linux
func isWSL() (bool, error) {
if runtime.GOOS == goosDarwin || runtime.GOOS == goosWindows {
return false, nil
}
data, err := ioutil.ReadFile("/proc/version")
if err != nil {
return false, xerrors.Errorf("read /proc/version: %w", err)
}
return strings.Contains(strings.ToLower(string(data)), "microsoft"), nil
}

// openURL opens the provided URL via user's default browser
func openURL(url string) error {
var cmd string
var args []string

wsl, err := isWSL()
if err != nil {
return xerrors.Errorf("test running Windows Subsystem for Linux: %w", err)
}

if wsl {
cmd = "cmd.exe"
args = []string{"/c", "start"}
url = strings.ReplaceAll(url, "&", "^&")
args = append(args, url)
return exec.Command(cmd, args...).Start()
}

return browser.OpenURL(url)
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ require (
github.com/pion/stun v0.3.5 // indirect
github.com/pion/turn/v2 v2.0.6 // indirect
github.com/pion/udp v0.1.1 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,7 @@ github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M
github.com/pion/webrtc/v3 v3.1.23 h1:suyNiF9o2/6SBsyWA1UweraUWYkaHCNJdt/16b61I5w=
github.com/pion/webrtc/v3 v3.1.23/go.mod h1:L5S/oAhL0Fzt/rnftVQRrP80/j5jygY7XRZzWwFx6P4=
github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
Expand Down