diff --git a/cli/login.go b/cli/login.go index d4238c4d3df11..e16118dfec0d6 100644 --- a/cli/login.go +++ b/cli/login.go @@ -41,16 +41,18 @@ func (r *RootCmd) login() *clibase.Cmd { const firstUserTrialEnv = "CODER_FIRST_USER_TRIAL" var ( - email string - username string - password string - trial bool + email string + username string + password string + trial bool + useTokenForSession bool ) cmd := &clibase.Cmd{ Use: "login ", Short: "Authenticate with Coder deployment", Middleware: clibase.RequireRangeArgs(0, 1), Handler: func(inv *clibase.Invocation) error { + ctx := inv.Context() rawURL := "" if len(inv.Args) == 0 { rawURL = r.clientURL.String() @@ -89,7 +91,7 @@ func (r *RootCmd) login() *clibase.Cmd { _, _ = fmt.Fprintln(inv.Stderr, cliui.DefaultStyles.Warn.Render(err.Error())) } - hasInitialUser, err := client.HasFirstUser(inv.Context()) + hasInitialUser, err := client.HasFirstUser(ctx) if err != nil { return xerrors.Errorf("Failed to check server %q for first user, is the URL correct and is coder accessible from your browser? Error - has initial user: %w", serverURL.String(), err) } @@ -182,7 +184,7 @@ func (r *RootCmd) login() *clibase.Cmd { trial = v == "yes" || v == "y" } - _, err = client.CreateFirstUser(inv.Context(), codersdk.CreateFirstUserRequest{ + _, err = client.CreateFirstUser(ctx, codersdk.CreateFirstUserRequest{ Email: email, Username: username, Password: password, @@ -191,7 +193,7 @@ func (r *RootCmd) login() *clibase.Cmd { if err != nil { return xerrors.Errorf("create initial user: %w", err) } - resp, err := client.LoginWithPassword(inv.Context(), codersdk.LoginWithPasswordRequest{ + resp, err := client.LoginWithPassword(ctx, codersdk.LoginWithPasswordRequest{ Email: email, Password: password, }) @@ -235,7 +237,7 @@ func (r *RootCmd) login() *clibase.Cmd { Secret: true, Validate: func(token string) error { client.SetSessionToken(token) - _, err := client.User(inv.Context(), codersdk.Me) + _, err := client.User(ctx, codersdk.Me) if err != nil { return xerrors.New("That's not a valid token!") } @@ -245,11 +247,27 @@ func (r *RootCmd) login() *clibase.Cmd { 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 } // Login to get user data - verify it is OK before persisting client.SetSessionToken(sessionToken) - resp, err := client.User(inv.Context(), codersdk.Me) + resp, err := client.User(ctx, codersdk.Me) if err != nil { return xerrors.Errorf("get user: %w", err) } @@ -293,6 +311,11 @@ func (r *RootCmd) login() *clibase.Cmd { Description: "Specifies whether a trial license should be provisioned for the Coder deployment or not.", Value: clibase.BoolOf(&trial), }, + { + 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.", + Value: clibase.BoolOf(&useTokenForSession), + }, } return cmd } diff --git a/cli/login_test.go b/cli/login_test.go index f8e24170c641e..1bab4721ea181 100644 --- a/cli/login_test.go +++ b/cli/login_test.go @@ -197,6 +197,7 @@ func TestLogin(t *testing.T) { <-doneChan }) + // TokenFlag should generate a new session token and store it in the session file. t.Run("TokenFlag", func(t *testing.T) { t.Parallel() client := coderdtest.New(t, nil) @@ -206,6 +207,7 @@ func TestLogin(t *testing.T) { require.NoError(t, err) sessionFile, err := cfg.Session().Read() require.NoError(t, err) - require.Equal(t, client.SessionToken(), sessionFile) + // This **should not be equal** to the token we passed in. + require.NotEqual(t, client.SessionToken(), sessionFile) }) } diff --git a/cli/testdata/coder_login_--help.golden b/cli/testdata/coder_login_--help.golden index 7169b369fce1d..bb458ca999854 100644 --- a/cli/testdata/coder_login_--help.golden +++ b/cli/testdata/coder_login_--help.golden @@ -19,5 +19,9 @@ Authenticate with Coder deployment Specifies a username to use if creating the first user for the deployment. + --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. + --- Run `coder --help` for a list of global options. diff --git a/codersdk/apikey.go b/codersdk/apikey.go index 95f9458043e87..514b519f5ffda 100644 --- a/codersdk/apikey.go +++ b/codersdk/apikey.go @@ -78,7 +78,10 @@ func (c *Client) CreateToken(ctx context.Context, userID string, req CreateToken } // CreateAPIKey generates an API key for the user ID provided. -// DEPRECATED: use CreateToken instead. +// CreateToken should be used over CreateAPIKey. CreateToken allows better +// tracking of the token's usage and allows for custom expiration. +// Only use CreateAPIKey if you want to emulate the session created for +// a browser like login. func (c *Client) CreateAPIKey(ctx context.Context, user string) (GenerateAPIKeyResponse, error) { res, err := c.Request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/users/%s/keys", user), nil) if err != nil { diff --git a/docs/cli/login.md b/docs/cli/login.md index 7af68c30e8de2..f7604d42db7b0 100644 --- a/docs/cli/login.md +++ b/docs/cli/login.md @@ -47,3 +47,11 @@ Specifies whether a trial license should be provisioned for the Coder deployment | Environment | $CODER_FIRST_USER_USERNAME | Specifies a username to use if creating the first user for the deployment. + +### --use-token-as-session + +| | | +| ---- | ----------------- | +| Type | 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.