Skip to content

Commit 093ba87

Browse files
Remove keychain storage, store tokens in config file (#269)
* Remove keychain storage, store tokens in config file - Drop internal/secrets/ package and keychain integration - Simplify config manager to use TOML-only token storage - Default host when config is empty, backfill user from server - Fix subcommand help delegation Signed-off-by: Jai Pradeesh <jai@deepsource.io> * Bump version to 2.0.38 * Clean up comments, refactor auth status, add tests - Remove redundant godoc and inline comments across codebase - Extract auth status into smaller helper methods - Add BackfillUser and default host logic to config manager - Add tests for login edge cases, auth status, and config manager * Bump version to 2.0.39 * Clean up report package and fix login test - Remove unused git.go and query.go from command/report - Fix flaky assertion in TestLoginConfigLoadError * Bump version to 2.0.40 --------- Signed-off-by: Jai Pradeesh <jai@deepsource.io>
1 parent e34608a commit 093ba87

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+452
-17838
lines changed

.github/workflows/CI.yml

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,10 @@ jobs:
3030
- name: Build the binary
3131
run: just build
3232

33-
- name: Setup tests
34-
run: just test-setup
35-
env:
36-
CODE_PATH: /home/runner/code
37-
3833
- name: Run tests
3934
run: just test
40-
env:
41-
CODE_PATH: /home/runner/code
4235

4336
- name: Report test coverage to DeepSource
4437
run: |
45-
curl https://deepsource.io/cli | sh
38+
curl -fsSL https://cli.deepsource.com/install | BINDIR=./bin sh
4639
./bin/deepsource report --analyzer test-coverage --key go --value-file ./coverage.out --use-oidc

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.0.37
1+
2.0.40

buildinfo/version.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,8 @@ var buildInfo *BuildInfo
99

1010
// App identity variables. Defaults are prod values; overridden in main.go for dev builds.
1111
var (
12-
AppName = "deepsource" // binary name / display name
13-
ConfigDirName = ".deepsource" // ~/<this>/
14-
KeychainSvc = "deepsource-cli" // macOS keychain service
15-
KeychainKey = "deepsource-cli-token" // macOS keychain account
12+
AppName = "deepsource" // binary name / display name
13+
ConfigDirName = ".deepsource" // ~/<this>/
1614
)
1715

1816
// BuildInfo describes the compile time information.
@@ -25,7 +23,6 @@ type BuildInfo struct {
2523
BuildMode string `json:"build_mode,omitempty"`
2624
}
2725

28-
// SetBuildInfo sets the build info as a package global.
2926
func SetBuildInfo(version, dateStr, buildMode string) {
3027
date, _ := time.Parse("2006-01-02T15:04:05Z", dateStr)
3128

@@ -36,7 +33,6 @@ func SetBuildInfo(version, dateStr, buildMode string) {
3633
}
3734
}
3835

39-
// GetBuildInfo returns the package global `buildInfo`
4036
func GetBuildInfo() *BuildInfo {
4137
return buildInfo
4238
}

cmd/deepsource/main.go

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,9 @@ import (
1616
)
1717

1818
var (
19-
// Version is the build version. This is set using ldflags -X
20-
version = "development"
21-
22-
// Date is the build date. This is set using ldflags -X
23-
Date = "YYYY-MM-DD" // YYYY-MM-DD
24-
25-
// DSN used for sentry
19+
version = "development"
20+
Date = "YYYY-MM-DD"
2621
SentryDSN string
27-
28-
// buildMode is "dev" or "prod" (default). Set via ldflags -X.
2922
buildMode string
3023
)
3124

@@ -47,8 +40,6 @@ func mainRun() (exitCode int) {
4740
if buildMode == "dev" {
4841
v.AppName = "deepsource-dev"
4942
v.ConfigDirName = ".deepsource-dev"
50-
v.KeychainSvc = "deepsource-dev-cli"
51-
v.KeychainKey = "deepsource-dev-cli-token"
5243
}
5344

5445
// Init sentry

command/auth/auth.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,8 @@ import (
88
"github.com/deepsourcelabs/cli/command/auth/status"
99
)
1010

11-
// Options holds the metadata.
1211
type Options struct{}
1312

14-
// NewCmdAuth handles the auth command which has various sub-commands like `login`, `logout` and `status`
1513
func NewCmdAuth() *cobra.Command {
1614
cmd := &cobra.Command{
1715
Use: "auth",

command/auth/login/login.go

Lines changed: 17 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import (
1717

1818
var accountTypes = []string{"DeepSource (deepsource.com)", "Enterprise Server"}
1919

20-
// LoginOptions hold the metadata related to login operation
2120
type LoginOptions struct {
2221
AuthTimedOut bool
2322
TokenExpired bool
@@ -28,12 +27,10 @@ type LoginOptions struct {
2827
deps *cmddeps.Deps
2928
}
3029

31-
// NewCmdLogin handles the login functionality for the CLI
3230
func NewCmdLogin() *cobra.Command {
3331
return NewCmdLoginWithDeps(nil)
3432
}
3533

36-
// NewCmdLoginWithDeps creates the login command with injectable dependencies.
3734
func NewCmdLoginWithDeps(deps *cmddeps.Deps) *cobra.Command {
3835
doc := heredoc.Docf(`
3936
Log in to DeepSource using the CLI.
@@ -66,7 +63,6 @@ func NewCmdLoginWithDeps(deps *cmddeps.Deps) *cobra.Command {
6663
},
6764
}
6865

69-
// --host flag (--hostname kept as deprecated alias)
7066
cmd.Flags().StringVar(&opts.HostName, "host", "", "Authenticate with a specific DeepSource instance")
7167
cmd.Flags().StringVar(&opts.HostName, "hostname", "", "Authenticate with a specific DeepSource instance")
7268
_ = cmd.Flags().MarkDeprecated("hostname", "use --host instead")
@@ -76,7 +72,6 @@ func NewCmdLoginWithDeps(deps *cmddeps.Deps) *cobra.Command {
7672
return cmd
7773
}
7874

79-
// Run executes the auth command and starts the login flow if not already authenticated
8075
func (opts *LoginOptions) Run() (err error) {
8176
var cfgMgr *config.Manager
8277
if opts.deps != nil && opts.deps.ConfigMgr != nil {
@@ -92,92 +87,78 @@ func (opts *LoginOptions) Run() (err error) {
9287
} else {
9388
svc = authsvc.NewService(cfgMgr)
9489
}
95-
// Fetch config (errors are non-fatal: a zero config just means "not logged in")
9690
cfg, err := svc.LoadConfig()
9791
if err != nil {
98-
cfg = &config.CLIConfig{}
92+
cfg = config.NewDefault()
9993
}
10094
opts.User = cfg.User
10195
opts.TokenExpired = cfg.IsExpired()
10296

103-
// If local says valid, verify against the server
104-
opts.verifyTokenWithServer(cfg, cfgMgr)
97+
opts.verifyTokenWithServer(cfg, svc)
10598

106-
// Login using the interactive mode
10799
if opts.Interactive {
108100
err = opts.handleInteractiveLogin()
109101
if err != nil {
110102
return err
111103
}
112104
}
113105

114-
// Checking if the user passed a hostname. If yes, storing it in the config
115-
// Else using the default hostname (deepsource.com)
116106
if opts.HostName != "" {
117107
cfg.Host = opts.HostName
118108
} else {
119109
cfg.Host = config.DefaultHostName
120110
}
121111

122-
// If PAT is passed, start the login flow through PAT (skip interactive prompts)
123112
if opts.PAT != "" {
124113
return opts.startPATLoginFlow(svc, cfg, opts.PAT)
125114
}
126115

127-
// Before starting the login workflow, check here for two conditions:
128-
// Condition 1 : If the token has expired, display a message about it and re-authenticate user
129-
// Condition 2 : If the token has not expired,does the user want to re-authenticate?
130-
131-
// Checking for condition 1
132116
if !opts.TokenExpired {
133-
// The user is already logged in, confirm re-authentication.
134-
msg := fmt.Sprintf("You're already logged into DeepSource as %s. Do you want to re-authenticate?", opts.User)
117+
var msg string
118+
if opts.User != "" {
119+
msg = fmt.Sprintf("You're already logged into DeepSource as %s. Do you want to re-authenticate?", opts.User)
120+
} else {
121+
msg = "You're already logged into DeepSource. Do you want to re-authenticate?"
122+
}
135123
response, err := prompt.ConfirmFromUser(msg, "")
136124
if err != nil {
137125
return fmt.Errorf("Error in fetching response. Please try again.")
138126
}
139-
// If the response is No, it implies that the user doesn't want to re-authenticate
140-
// In this case, just exit
141127
if !response {
142128
return nil
143129
}
144130
}
145131

146-
// Condition 2
147-
// `startLoginFlow` implements the authentication flow for the CLI
148132
return opts.startLoginFlow(svc, cfg)
149133
}
150134

151-
func (opts *LoginOptions) verifyTokenWithServer(cfg *config.CLIConfig, cfgMgr *config.Manager) {
152-
if opts.TokenExpired || cfg.Token == "" || cfg.Host == "" {
135+
func (opts *LoginOptions) verifyTokenWithServer(cfg *config.CLIConfig, svc *authsvc.Service) {
136+
if opts.TokenExpired || cfg.Token == "" {
153137
return
154138
}
155-
client, err := deepsource.New(deepsource.ClientOpts{
156-
Token: cfg.Token,
157-
HostName: cfg.Host,
158-
OnTokenRefreshed: cfgMgr.TokenRefreshCallback(),
159-
})
139+
viewer, err := svc.GetViewer(context.Background(), cfg)
160140
if err != nil {
141+
opts.TokenExpired = true
161142
return
162143
}
163-
if _, err := client.GetViewer(context.Background()); err != nil {
164-
opts.TokenExpired = true
144+
// Backfill user from the server when config file was missing or incomplete.
145+
if cfg.User == "" && viewer.Email != "" {
146+
cfg.User = viewer.Email
147+
opts.User = viewer.Email
148+
_ = svc.SaveConfig(cfg)
165149
}
166150
}
167151

168152
func (opts *LoginOptions) handleInteractiveLogin() error {
169-
// Prompt messages and help texts
170153
loginPromptMessage := "Which account do you want to login into?"
171154
loginPromptHelpText := "Select the type of account you want to authenticate"
172155
hostPromptMessage := "Please enter the hostname:"
173156
hostPromptHelpText := "The hostname of the DeepSource instance to authenticate with"
174157

175-
// Display prompt to user
176158
loginType, err := prompt.SelectFromOptions(loginPromptMessage, loginPromptHelpText, accountTypes)
177159
if err != nil {
178160
return err
179161
}
180-
// Prompt the user for hostname only in the case of on-premise
181162
if loginType == "Enterprise Server" {
182163
opts.HostName, err = prompt.GetSingleLineInput(hostPromptMessage, hostPromptHelpText)
183164
if err != nil {

command/auth/login/login_flow.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,13 @@ import (
1515
"github.com/fatih/color"
1616
)
1717

18-
// Starts the login flow for the CLI
1918
func (opts *LoginOptions) startLoginFlow(svc *authsvc.Service, cfg *config.CLIConfig) error {
20-
// Register the device and get a device code through the response
2119
ctx := context.Background()
2220
deviceRegistrationResponse, err := registerDevice(ctx, svc, cfg)
2321
if err != nil {
2422
return err
2523
}
2624

27-
// Open the browser for authentication
2825
err = browser.OpenURL(deviceRegistrationResponse.VerificationURIComplete)
2926
if err != nil {
3027
c := color.New(color.FgCyan, color.Bold)
@@ -38,7 +35,6 @@ func (opts *LoginOptions) startLoginFlow(svc *authsvc.Service, cfg *config.CLICo
3835
fmt.Println()
3936
fmt.Println("Waiting for authentication")
4037

41-
// Fetch the PAT by polling the server
4238
var tokenData *auth.PAT
4339
tokenData, opts.AuthTimedOut, err = fetchPAT(ctx, deviceRegistrationResponse, svc, cfg)
4440
if err != nil {
@@ -49,7 +45,6 @@ func (opts *LoginOptions) startLoginFlow(svc *authsvc.Service, cfg *config.CLICo
4945
return clierrors.ErrAuthTimeout()
5046
}
5147

52-
// Store auth data in config
5348
cfg.User = tokenData.User.Email
5449
cfg.Token = tokenData.Token
5550
cfg.SetTokenExpiry(tokenData.Expiry)
@@ -76,9 +71,6 @@ func fetchPAT(ctx context.Context, deviceRegistrationData *auth.Device, svc *aut
7671
userName := ""
7772
authTimedOut := true
7873

79-
/* ======================================================================= */
80-
// The username and hostname to add in the description for the PAT request
81-
/* ======================================================================= */
8274
userData, err := user.Current()
8375
if err != nil {
8476
userName = defaultUserName
@@ -92,11 +84,9 @@ func fetchPAT(ctx context.Context, deviceRegistrationData *auth.Device, svc *aut
9284
}
9385
userDescription := fmt.Sprintf("CLI PAT for %s@%s", userName, hostName)
9486

95-
// Keep polling the mutation at a certain interval till the expiry timeperiod
9687
ticker := time.NewTicker(time.Duration(deviceRegistrationData.Interval) * time.Second)
9788
pollStartTime := time.Now()
9889

99-
// Polling for fetching PAT
10090
func() {
10191
for range ticker.C {
10292
tokenData, err = svc.RequestPAT(ctx, cfg, deviceRegistrationData.Code, userDescription)

command/auth/login/pat_login_flow.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,15 @@ import (
99
"github.com/fatih/color"
1010
)
1111

12-
// Starts the login flow for the CLI (using PAT)
1312
func (opts *LoginOptions) startPATLoginFlow(svc *authsvc.Service, cfg *config.CLIConfig, token string) error {
14-
// set personal access token (PAT)
1513
cfg.Token = token
1614

17-
// Verify the token against the server before saving
1815
viewer, err := svc.GetViewer(context.Background(), cfg)
1916
if err != nil {
2017
return fmt.Errorf("Invalid token: could not authenticate with DeepSource")
2118
}
2219
cfg.User = viewer.Email
2320

24-
// Having stored the data in the global Cfg object, write it into the config file present in the local filesystem
2521
err = svc.SaveConfig(cfg)
2622
if err != nil {
2723
return fmt.Errorf("Error in writing authentication data to a file. Exiting..")

0 commit comments

Comments
 (0)