mirror of https://github.com/coder/coder.git
Merge 341feddf1b
into 93d8812284
This commit is contained in:
commit
60d3613a0e
77
cli/login.go
77
cli/login.go
|
@ -274,7 +274,52 @@ func (r *RootCmd) login() *serpent.Command {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Check for session token from flags or environment.
|
||||
sessionToken, _ := inv.ParsedFlags().GetString(varToken)
|
||||
if sessionToken != "" && !useTokenForSession {
|
||||
// If a session token is provided on the cli, use it to generate
|
||||
// a new one. This is because the cli `--token` flag provides
|
||||
// a token for the command being invoked. We should not store
|
||||
// this token, and `/logout` should not delete it.
|
||||
// /login should generate a new token and store that.
|
||||
client.SetSessionToken(sessionToken)
|
||||
// Use CreateAPIKey over CreateToken because this is a session
|
||||
// key that should not show on the `tokens` page. This should
|
||||
// match the same behavior of the `/cli-auth` page for generating
|
||||
// a session token.
|
||||
key, err := client.CreateAPIKey(ctx, codersdk.Me)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create api key: %w", err)
|
||||
}
|
||||
sessionToken = key.Key
|
||||
}
|
||||
|
||||
// Check for existing session token on disk, and validate user data.
|
||||
// If the token exists but is invalid, then it is probably expired.
|
||||
// Skip this check if the user has provided a valid token; a new token
|
||||
// should be generated.
|
||||
var userResp codersdk.User
|
||||
if configToken, _ := r.createConfig().Session().Read(); sessionToken == "" && configToken != "" {
|
||||
client.SetSessionToken(configToken)
|
||||
userResp, err = client.User(ctx, codersdk.Me)
|
||||
if err == nil {
|
||||
_, err = cliui.Prompt(inv, cliui.PromptOptions{
|
||||
Text: fmt.Sprintf("You are already authenticated %s. Are you sure you want to log in again?",
|
||||
pretty.Sprint(cliui.DefaultStyles.Keyword, userResp.Username)),
|
||||
IsConfirm: true,
|
||||
Default: cliui.ConfirmYes,
|
||||
})
|
||||
if errors.Is(err, cliui.Canceled) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
sessionToken = ""
|
||||
}
|
||||
|
||||
// If we still don't have a session token, prompt the user for one.
|
||||
if sessionToken == "" {
|
||||
authURL := *serverURL
|
||||
// Don't use filepath.Join, we don't want to use the os separator
|
||||
|
@ -300,29 +345,16 @@ func (r *RootCmd) login() *serpent.Command {
|
|||
if err != nil {
|
||||
return xerrors.Errorf("paste token prompt: %w", err)
|
||||
}
|
||||
} else if !useTokenForSession {
|
||||
// If a session token is provided on the cli, use it to generate
|
||||
// a new one. This is because the cli `--token` flag provides
|
||||
// a token for the command being invoked. We should not store
|
||||
// this token, and `/logout` should not delete it.
|
||||
// /login should generate a new token and store that.
|
||||
client.SetSessionToken(sessionToken)
|
||||
// Use CreateAPIKey over CreateToken because this is a session
|
||||
// key that should not show on the `tokens` page. This should
|
||||
// match the same behavior of the `/cli-auth` page for generating
|
||||
// a session token.
|
||||
key, err := client.CreateAPIKey(ctx, "me")
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create api key: %w", err)
|
||||
}
|
||||
sessionToken = key.Key
|
||||
}
|
||||
|
||||
// If we didn't login via session token on disk
|
||||
// Login to get user data - verify it is OK before persisting
|
||||
client.SetSessionToken(sessionToken)
|
||||
resp, err := client.User(ctx, codersdk.Me)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get user: %w", err)
|
||||
if userResp.Email == "" {
|
||||
client.SetSessionToken(sessionToken)
|
||||
userResp, err = client.User(ctx, codersdk.Me)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get user: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
config := r.createConfig()
|
||||
|
@ -335,7 +367,8 @@ func (r *RootCmd) login() *serpent.Command {
|
|||
return xerrors.Errorf("write server url: %w", err)
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(inv.Stdout, Caret+"Welcome to Coder, %s! You're authenticated.\n", pretty.Sprint(cliui.DefaultStyles.Keyword, resp.Username))
|
||||
_, _ = fmt.Fprintf(inv.Stdout, Caret+"Welcome to Coder, %s! You're authenticated.\n",
|
||||
pretty.Sprint(cliui.DefaultStyles.Keyword, userResp.Username))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
@ -366,7 +399,7 @@ func (r *RootCmd) login() *serpent.Command {
|
|||
},
|
||||
{
|
||||
Flag: "use-token-as-session",
|
||||
Description: "By default, the CLI will generate a new session token when logging in. This flag will instead use the provided token as the session token.",
|
||||
Description: "By default, the CLI will generate a new session token when logging in. This flag will instead use the provided token as the session token. See `coder --help` for more information on how to set a token.",
|
||||
Value: serpent.BoolOf(&useTokenForSession),
|
||||
},
|
||||
}
|
||||
|
|
|
@ -225,6 +225,8 @@ func TestLogin(t *testing.T) {
|
|||
|
||||
inv, root := clitest.New(t, "login", "--no-open")
|
||||
clitest.SetupConfig(t, client, root)
|
||||
err := root.Session().Delete()
|
||||
assert.NoError(t, err)
|
||||
|
||||
doneChan := make(chan struct{})
|
||||
pty := ptytest.New(t).Attach(inv)
|
||||
|
@ -291,6 +293,110 @@ func TestLogin(t *testing.T) {
|
|||
<-doneChan
|
||||
})
|
||||
|
||||
t.Run("ExistingUserExpiredSessionToken", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
inv, root := clitest.New(t, "login", "--no-open", client.URL.String())
|
||||
pty := ptytest.New(t).Attach(inv)
|
||||
|
||||
err := root.Session().Write("an-expired-token")
|
||||
assert.NoError(t, err)
|
||||
|
||||
doneChan := make(chan struct{})
|
||||
go func() {
|
||||
defer close(doneChan)
|
||||
err := inv.Run()
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
|
||||
pty.ExpectMatch("Paste your token here:")
|
||||
pty.WriteLine(client.SessionToken())
|
||||
pty.ExpectMatch("Welcome to Coder")
|
||||
<-doneChan
|
||||
})
|
||||
|
||||
t.Run("ExistingUserRelogin", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
inv, root := clitest.New(t, "login", "--no-open")
|
||||
clitest.SetupConfig(t, client, root)
|
||||
pty := ptytest.New(t).Attach(inv)
|
||||
|
||||
doneChan := make(chan struct{})
|
||||
go func() {
|
||||
defer close(doneChan)
|
||||
err := inv.Run()
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
|
||||
pty.ExpectMatch("You are already authenticated")
|
||||
pty.ExpectMatch("Are you sure you want to log in again?")
|
||||
pty.WriteLine("yes")
|
||||
pty.ExpectMatch("Paste your token here:")
|
||||
pty.WriteLine(client.SessionToken())
|
||||
pty.ExpectMatch("Welcome to Coder")
|
||||
<-doneChan
|
||||
})
|
||||
|
||||
t.Run("ExistingUserReloginRejection", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
inv, root := clitest.New(t, "login", "--no-open")
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
||||
doneChan := make(chan struct{})
|
||||
pty := ptytest.New(t).Attach(inv)
|
||||
go func() {
|
||||
defer close(doneChan)
|
||||
err := inv.Run()
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
|
||||
pty.ExpectMatch("You are already authenticated")
|
||||
pty.ExpectMatch("Are you sure you want to log in again?")
|
||||
pty.WriteLine("no")
|
||||
<-doneChan
|
||||
})
|
||||
|
||||
t.Run("AuthenticatedUserTokenFlagValid", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
inv, root := clitest.New(t, "login", client.URL.String(), "--no-open", "--token", client.SessionToken())
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
||||
err := inv.Run()
|
||||
assert.NoError(t, err)
|
||||
|
||||
sessionFile, err := root.Session().Read()
|
||||
require.NoError(t, err)
|
||||
// This **should not be equal** to the token we passed in.
|
||||
require.NotEqual(t, client.SessionToken(), sessionFile)
|
||||
})
|
||||
|
||||
t.Run("AuthenticatedUserTokenFlagValidUseTokenAsSession", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
inv, root := clitest.New(t, "login", client.URL.String(), "--no-open", "--token", client.SessionToken(), "--use-token-as-session")
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
||||
err := inv.Run()
|
||||
assert.NoError(t, err)
|
||||
|
||||
sessionFile, err := root.Session().Read()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, client.SessionToken(), sessionFile)
|
||||
})
|
||||
|
||||
// TokenFlag should generate a new session token and store it in the session file.
|
||||
t.Run("TokenFlag", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
|
|
@ -25,6 +25,7 @@ OPTIONS:
|
|||
--use-token-as-session bool
|
||||
By default, the CLI will generate a new session token when logging in.
|
||||
This flag will instead use the provided token as the session token.
|
||||
See `coder --help` for more information on how to set a token.
|
||||
|
||||
———
|
||||
Run `coder --help` for a list of global options.
|
||||
|
|
|
@ -54,4 +54,4 @@ Specifies whether a trial license should be provisioned for the Coder deployment
|
|||
| ---- | ----------------- |
|
||||
| Type | <code>bool</code> |
|
||||
|
||||
By default, the CLI will generate a new session token when logging in. This flag will instead use the provided token as the session token.
|
||||
By default, the CLI will generate a new session token when logging in. This flag will instead use the provided token as the session token. See `coder --help` for more information on how to set a token.
|
||||
|
|
Loading…
Reference in New Issue