Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
40b055a
(build) Update Golang OAuth2 Library to latest
christianarty Jul 8, 2025
b615be3
(feat) Implement OAuth2 func. into jira-cli
christianarty Jul 5, 2025
23a3c5d
feat:(oauth) - Ability to get cloud id for clients
christianarty Jul 9, 2025
eb98589
(docs) Add note about OAuth weirdness
christianarty Jul 12, 2025
722e4d9
(refactor) Move oauth to separate module
christianarty Jul 12, 2025
007f761
(refactor) Remove unnecessary args from `verifyLoginDetails` and `con…
christianarty Jul 12, 2025
e4cc1c2
(tests): Add tests for oauth
christianarty Jul 12, 2025
691edbb
(nit) Move getToken logic into separate func
christianarty Jul 13, 2025
f57474b
(refactor) Organizing the oauth to break up the logic to other files
christianarty Jul 15, 2025
40402e6
(feat) Add the ability to load the persisted oauth token from filesystem
christianarty Jul 15, 2025
ca6a100
(test) More tests for oauth
christianarty Jul 15, 2025
00d31d5
(feat) Enable auto refreshing access tokens through the oauth2 custom…
christianarty Jul 15, 2025
c5533a8
(nit) remove dead code
christianarty Jul 15, 2025
d7917e9
(fix) The constructed server urls were pointing to authorization serv…
christianarty Jul 17, 2025
2af8279
(docs) update README with more instructions.
christianarty Jul 17, 2025
605d9f0
(fix/cleanup) Remove old/redundant code and fix the oauth tests
christianarty Jul 17, 2025
7264fa9
(nit) Remove old TODO
christianarty Jul 17, 2025
b4b3d62
(lint) fix the lint issues
christianarty Jul 17, 2025
0d0e03a
(ci/fix) Fix tests to allow for ci to pass quality checks
christianarty Jul 17, 2025
aada4f6
(ci) Fix some more issues found with DeepSource
christianarty Jul 17, 2025
b9aa1be
(docs) update README to account for discussion post
christianarty Jul 19, 2025
bf3fce8
(nit) add additional scopes for properly reading jira sprint
christianarty Oct 8, 2025
99f1bb6
need this scope for adding to a sprint
christianarty Oct 8, 2025
6282035
typos
christianarty Oct 13, 2025
a59a503
address the searching for epic details
christianarty Oct 13, 2025
619badc
fix reason why the cloud isn't working
christianarty Oct 13, 2025
e171f6c
linting
christianarty Oct 13, 2025
e6104a8
reorganize the storage and filesystem tests
christianarty Nov 3, 2025
8de8754
add a keyring storage and some tests
christianarty Nov 3, 2025
f655fe8
filesystem should work similar to keyring
christianarty Nov 15, 2025
da55c8c
[test] add storage_test
christianarty Nov 15, 2025
772471d
update keyring storage to remove some unless comments
christianarty Nov 15, 2025
acf64fc
add a way for use a fallback storage for saving the token
christianarty Nov 15, 2025
b6caa86
(fix) separating the login details and the server details
christianarty Nov 15, 2025
0c6e24d
(nit) remove the testService in the keyring storage test
christianarty Nov 15, 2025
5cd847c
(fix) pass around the login field to the oauth token so the keyring w…
christianarty Nov 15, 2025
bc29aa8
(lint) make lint
christianarty Nov 15, 2025
fd7e189
nit: shouldn't pretty print an oauth file
christianarty Nov 16, 2025
1c9cd80
add a compression to the string before saving
christianarty Nov 16, 2025
761ef92
(fix) allow for tests to work and catch bug with loading json file
christianarty Nov 16, 2025
666d2aa
(feat) add env variable workarounds
christianarty Nov 16, 2025
f78deae
(nit) Rely on the keyring to emit the size error rather than us
christianarty Nov 16, 2025
606fedd
(feat) add warning for when the filesystem storage is used
christianarty Nov 16, 2025
471e1f4
(improv) the HTML page was ugly 🤷🏾♂️
christianarty Nov 16, 2025
2a83db3
(test) add the assertion that we fallback to fs when keyring fails
christianarty Nov 16, 2025
072b00d
(improv) moved warning to fs storage
christianarty Nov 16, 2025
63d5650
(fix) consistent env names, and add override for cloud id
christianarty Nov 16, 2025
662e4e6
update readme
christianarty Nov 16, 2025
35ed32a
(lint) fix errors
christianarty Nov 16, 2025
3acccb6
deepsource was yelling at me
christianarty Nov 16, 2025
ee1343f
(feat) print out expected scopes when initializing
christianarty Nov 16, 2025
5a751c7
(nit) minor addition to the printing of expected scopes
christianarty Nov 16, 2025
6680e93
(pr) rename cloudId to be consistent
christianarty Nov 17, 2025
f49ce94
(pr-nit) oauth canonical
christianarty Nov 17, 2025
3006834
(pr) use Jira AuthType for options
christianarty Nov 17, 2025
a1c1617
(pr) path escape cloudid
christianarty Nov 17, 2025
38c5fc4
(pr-fix) c.JiraClient was checking if !nil, when it should be
christianarty Nov 17, 2025
a1b4bcc
(pr) move and use accessibleURL to cloud_id
christianarty Nov 17, 2025
858d625
(pr) rename cloudid file
christianarty Nov 17, 2025
4be38c9
(pr) fix the way we set the clientID and ClientSecret from env
christianarty Nov 19, 2025
62878e2
(pr) refactor the scopes to be a struct to properly print and use
christianarty Nov 19, 2025
3362e6e
(nit) remove a useless test
christianarty Nov 19, 2025
a7270c6
(nit) this should be before setting the apiServer not after
christianarty Nov 19, 2025
bdabb55
Update README.md
christianarty Nov 19, 2025
69a45c3
Update pkg/utils/keyring_storage.go
christianarty Nov 19, 2025
00a3792
(pr-nit) oauth name
christianarty Nov 19, 2025
3b2414c
(pr-nit) add AccessibleURL
christianarty Nov 19, 2025
fb011af
(pr-nit) godoc comments
christianarty Nov 19, 2025
8eed942
(pr-nit) rename to follow conventions
christianarty Nov 19, 2025
8fe3460
(pr-improv) use slices.SortFunc for a simpler sort when printing expe…
christianarty Nov 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 103 additions & 16 deletions README.md

Large diffs are not rendered by default.

83 changes: 69 additions & 14 deletions api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,75 @@ import (
"github.com/spf13/viper"
"github.com/zalando/go-keyring"

"github.com/ankitpokhrel/jira-cli/internal/cmdutil"
"github.com/ankitpokhrel/jira-cli/pkg/jira"
"github.com/ankitpokhrel/jira-cli/pkg/jira/filter"
"github.com/ankitpokhrel/jira-cli/pkg/netrc"
"github.com/ankitpokhrel/jira-cli/pkg/oauth"
)

const clientTimeout = 15 * time.Second

var jiraClient *jira.Client

// getAPIToken retrieves the API token from various sources in order of priority:
// 1. Viper configuration
// 2. OAuth access token (if available and valid)
// 3. Netrc file
// 4. Keyring.
func getAPIToken(config *jira.Config) string {
if config.APIToken != "" {
return config.APIToken
}

// Try viper config first
if token := viper.GetString("api_token"); token != "" {
return token
}

// Try OAuth access token if available and valid
// And should only do this assertion if the AuthType is oauth
isAuthTypeOAuth := config.AuthType != nil && *config.AuthType == jira.AuthTypeOAuth
if isAuthTypeOAuth && oauth.HasOAuthCredentials(config.Login) {
tk, _ := oauth.LoadOAuth2TokenSource(config.Login)
token, _ := tk.Token()
return token.AccessToken
}

// Try netrc file
if netrcConfig, _ := netrc.Read(config.Server, config.Login); netrcConfig != nil {
if netrcConfig.Password != "" {
return netrcConfig.Password
}
}

// Try keyring
if secret, _ := keyring.Get("jira-cli", config.Login); secret != "" {
return secret
}

return ""
}

// Client initializes and returns jira client.
func Client(config jira.Config) *jira.Client {
if jiraClient != nil {
return jiraClient
}

if config.Server == "" {
config.Server = viper.GetString("server")
apiServer := viper.GetString("api_server")
if apiServer != "" {
config.Server = apiServer
} else {
// Fallback to server URL if api_server is not set
cmdutil.Warn("api_server key is not set, falling back to server URL")
config.Server = viper.GetString("server")
}
}
if config.Login == "" {
config.Login = viper.GetString("login")
}
if config.APIToken == "" {
config.APIToken = viper.GetString("api_token")
}
if config.APIToken == "" {
netrcConfig, _ := netrc.Read(config.Server, config.Login)
if netrcConfig != nil {
config.APIToken = netrcConfig.Password
}
}
if config.APIToken == "" {
secret, _ := keyring.Get("jira-cli", config.Login)
config.APIToken = secret
}
if config.AuthType == nil {
authType := jira.AuthType(viper.GetString("auth_type"))
config.AuthType = &authType
Expand All @@ -49,6 +84,26 @@ func Client(config jira.Config) *jira.Client {
config.Insecure = &insecure
}

// Check if we have OAuth credentials and should use OAuth
if oauth.HasOAuthCredentials(config.Login) && config.AuthType != nil && *config.AuthType == jira.AuthTypeOAuth {
// Try to create OAuth2 token source
tokenSource, err := oauth.LoadOAuth2TokenSource(config.Login)
if err == nil {
// We have valid OAuth credentials, use OAuth authentication
// Pass the TokenSource to the client via a custom option
jiraClient = jira.NewClient(
config,
jira.WithTimeout(clientTimeout),
jira.WithInsecureTLS(*config.Insecure),
jira.WithOAuth2TokenSource(tokenSource),
)
return jiraClient
}
}

// Get API token from various sources (fallback for non-OAuth auth)
config.APIToken = getAPIToken(&config)

// MTLS

if config.MTLSConfig.CaCert == "" {
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ require (
github.com/spf13/viper v1.18.2
github.com/stretchr/testify v1.10.0
github.com/zalando/go-keyring v0.2.6
golang.org/x/oauth2 v0.30.0
golang.org/x/term v0.30.0
)

Expand All @@ -39,6 +40,7 @@ require (
github.com/charmbracelet/x/ansi v0.8.0 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/cli/browser v1.3.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
github.com/creack/pty v1.1.18 // indirect
github.com/danieljoos/wincred v1.2.2 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99k
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo=
github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk=
github.com/cli/safeexec v1.0.1 h1:e/C79PbXF4yYTN/wauC4tviMxEV13BwljGj0N9j+N00=
github.com/cli/safeexec v1.0.1/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
Expand Down Expand Up @@ -187,6 +189,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down
5 changes: 5 additions & 0 deletions internal/cmd/root/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
jiraConfig "github.com/ankitpokhrel/jira-cli/internal/config"
"github.com/ankitpokhrel/jira-cli/pkg/jira"
"github.com/ankitpokhrel/jira-cli/pkg/netrc"
"github.com/ankitpokhrel/jira-cli/pkg/oauth"

"github.com/zalando/go-keyring"
)
Expand Down Expand Up @@ -156,6 +157,10 @@ func cmdRequireToken(cmd string) bool {
}

func checkForJiraToken(server string, login string) {
if oauth.HasOAuthCredentials(login) {
return
}

if os.Getenv("JIRA_API_TOKEN") != "" {
return
}
Expand Down
Loading