Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,10 @@ pass, and an existing configuration is carried over the first time dwshell saves
anything.

```sh
$ dwshell login --user [email protected] # the email is the account's identity
$ dwshell login # the email is the account's identity
User (email) [[email protected]]: [email protected]
Password: …

$ dwshell account list
[email protected] (default)
[email protected]
Expand All @@ -269,6 +272,11 @@ $ dwshell list --account [email protected] # the other one
$ [email protected] dwshell list # for a whole session
```

`login` asks which email to use, offering the account you already have: press
Enter to refresh it, or type another to register a second. `--user` answers the
question up front, and without a terminal — a script — the default account is
used and nothing is asked.

The first account you log in with becomes the default; change it with
`dwshell account default <email>`. Logging in again with an email already
registered just refreshes that account.
Expand Down
70 changes: 70 additions & 0 deletions cmd/dwshell/login_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package main

import "testing"

// asking records what the prompt was offered as a default, and answers with a
// canned reply, standing in for a person at a terminal.
func asking(answer string, seen *string) func(string) string {
return func(suggested string) string {
*seen = suggested
return answer
}
}

func TestLoginUserPrefersTheFlag(t *testing.T) {
var asked string
got := loginUser("typed@x", "default@x", true, asking("ignored@x", &asked))
if got != "typed@x" {
t.Fatalf("user = %q, want the --user value", got)
}
if asked != "" {
t.Error("with --user given there is nothing to ask")
}
}

// The whole point: at a terminal the email is asked, so a bare `dwshell login`
// can register an account that is not the default one.
func TestLoginUserAsksSoAnAccountCanBeAdded(t *testing.T) {
var asked string
got := loginUser("", "default@x", true, asking("second@x", &asked))
if got != "second@x" {
t.Fatalf("user = %q: a new email typed at the prompt must be honoured", got)
}
if asked != "default@x" {
t.Errorf("the prompt should offer the default account, offered %q", asked)
}
}

// Answering nothing accepts the default, so refreshing the account you already
// have stays a single keystroke.
func TestLoginUserEmptyAnswerKeepsTheDefault(t *testing.T) {
var asked string
got := loginUser("", "default@x", true, asking("", &asked))
if got != "default@x" {
t.Fatalf("user = %q, want the default", got)
}
}

// With no terminal there is nobody to ask, so a script refreshing the default
// account keeps working exactly as before.
func TestLoginUserFallsBackToTheDefaultWithoutATerminal(t *testing.T) {
var asked string
got := loginUser("", "default@x", false, asking("unused@x", &asked))
if got != "default@x" {
t.Fatalf("user = %q, want the default", got)
}
if asked != "" {
t.Error("nothing may be asked when there is no terminal")
}
}

func TestLoginUserAsksWithNothingConfigured(t *testing.T) {
var asked string
got := loginUser("", "", true, asking("first@x", &asked))
if got != "first@x" {
t.Fatalf("user = %q", got)
}
if asked != "" {
t.Errorf("with no account there is no default to offer, offered %q", asked)
}
}
44 changes: 36 additions & 8 deletions cmd/dwshell/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,14 +196,7 @@ func cmdLogin(ctx context.Context, args []string) int {
if err != nil {
return fail("%v", err)
}
if user == "" {
// Re-logging in with no --user refreshes the default account.
user = c.Config().Default
}
if user == "" {
fmt.Fprint(os.Stderr, "User (email): ")
fmt.Scanln(&user)
}
user = loginUser(user, c.Config().Default, term.IsTTY(), promptUser)
if user == "" {
return fail("a user is required")
}
Expand All @@ -223,6 +216,41 @@ func cmdLogin(ctx context.Context, args []string) int {
return 0
}

// loginUser decides which account `login` authenticates.
//
// The email is asked for whenever there is someone to ask, because it is the
// account's identity: skipping the question when an account already exists
// would make a bare `dwshell login` able only ever to refresh that one, never
// to register a second. The account already configured is offered as the
// answer, so refreshing it stays one keystroke.
//
// With no terminal — a script — there is nobody to ask, so the default account
// is used, which is what such a script has always done.
func loginUser(flagUser, defaultAccount string, interactive bool, prompt func(suggested string) string) string {
if flagUser != "" {
return flagUser
}
if !interactive {
return defaultAccount
}
if answer := strings.TrimSpace(prompt(defaultAccount)); answer != "" {
return answer
}
return defaultAccount
}

// promptUser asks for the account email, offering the one already configured.
func promptUser(suggested string) string {
if suggested != "" {
fmt.Fprintf(os.Stderr, "User (email) [%s]: ", suggested)
} else {
fmt.Fprint(os.Stderr, "User (email): ")
}
var answer string
fmt.Scanln(&answer)
return answer
}

func readPassword() (string, error) {
if p := os.Getenv("DWSHELL_PASSWORD"); p != "" {
return p, nil
Expand Down