This commit is contained in:
elasticspoon 2024-05-01 21:47:29 +00:00 committed by GitHub
commit 60d3613a0e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 163 additions and 23 deletions

View File

@ -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),
},
}

View File

@ -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()

View File

@ -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.

2
docs/cli/login.md generated
View File

@ -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.