mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
139dab7cfe
This change implements optional secure storage of the CLI token using the operating system keyring for Windows, with groundwork laid for macOS in a future change. Previously, the Coder CLI stored authentication tokens in plaintext configuration files, which posed a security risk because users' tokens are stored unencrypted and can be easily accessed by other processes or users with file system access. The keyring is opt-in to preserve compatibility with applications (like the JetBrains Toolbox plugin, VS code plugin, etc). Users can opt into keyring use with a new `--use-keyring` flag. The secure storage is platform dependent. Windows Credential Manager API is used on Windows. The session token continues to be stored in plain text on macOS and Linux. macOS is omitted for now while we figure out the best path forward for compatibility with apps like Coder Desktop. https://www.notion.so/coderhq/CLI-Session-Token-in-OS-Keyring-293d579be592808b8b7fd235304e50d5 https://github.com/coder/coder/issues/19403
82 lines
2.3 KiB
Go
82 lines
2.3 KiB
Go
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/coder/coder/v2/cli/cliui"
|
|
"github.com/coder/coder/v2/cli/sessionstore"
|
|
"github.com/coder/serpent"
|
|
)
|
|
|
|
func (r *RootCmd) logout() *serpent.Command {
|
|
cmd := &serpent.Command{
|
|
Use: "logout",
|
|
Short: "Unauthenticate your local session",
|
|
Handler: func(inv *serpent.Invocation) error {
|
|
client, err := r.InitClient(inv)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var errors []error
|
|
|
|
config := r.createConfig()
|
|
|
|
_, err = cliui.Prompt(inv, cliui.PromptOptions{
|
|
Text: "Are you sure you want to log out?",
|
|
IsConfirm: true,
|
|
Default: cliui.ConfirmYes,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = client.Logout(inv.Context())
|
|
if err != nil {
|
|
errors = append(errors, xerrors.Errorf("logout api: %w", err))
|
|
}
|
|
|
|
err = config.URL().Delete()
|
|
// Only throw error if the URL configuration file is present,
|
|
// otherwise the user is already logged out, and we proceed
|
|
if err != nil && !os.IsNotExist(err) {
|
|
errors = append(errors, xerrors.Errorf("remove URL file: %w", err))
|
|
}
|
|
|
|
err = r.ensureTokenBackend().Delete(client.URL)
|
|
// Only throw error if the session configuration file is present,
|
|
// otherwise the user is already logged out, and we proceed
|
|
if err != nil && !xerrors.Is(err, os.ErrNotExist) {
|
|
if xerrors.Is(err, sessionstore.ErrNotImplemented) {
|
|
errors = append(errors, errKeyringNotSupported)
|
|
} else {
|
|
errors = append(errors, xerrors.Errorf("remove session token: %w", err))
|
|
}
|
|
}
|
|
|
|
err = config.Organization().Delete()
|
|
// If the organization configuration file is absent, we still proceed
|
|
if err != nil && !os.IsNotExist(err) {
|
|
errors = append(errors, xerrors.Errorf("remove organization file: %w", err))
|
|
}
|
|
|
|
if len(errors) > 0 {
|
|
var errorStringBuilder strings.Builder
|
|
for _, err := range errors {
|
|
_, _ = fmt.Fprint(&errorStringBuilder, "\t"+err.Error()+"\n")
|
|
}
|
|
errorString := strings.TrimRight(errorStringBuilder.String(), "\n")
|
|
return xerrors.New("Failed to log out.\n" + errorString)
|
|
}
|
|
_, _ = fmt.Fprint(inv.Stdout, Caret+"You are no longer logged in. You can log in using 'coder login <url>'.\n")
|
|
return nil
|
|
},
|
|
}
|
|
cmd.Options = append(cmd.Options, cliui.SkipPromptOption())
|
|
return cmd
|
|
}
|