Skip to content

Commit

Permalink
feat(oauth): save resources info in config dir
Browse files Browse the repository at this point in the history
  • Loading branch information
HandOfGod94 committed Oct 21, 2023
1 parent 015d49f commit 71b8957
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 8 deletions.
5 changes: 5 additions & 0 deletions cmd/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package cmd
import (
"context"
"fmt"
"os"

"github.com/fatih/color"
"github.com/handofgod94/gh-jira-changelog/pkg/jira_changelog/jira/auth"
"github.com/spf13/cobra"
)
Expand All @@ -22,8 +24,11 @@ as Atlassian currently doesn't support PKCE verification for oauth flow.`,
case "login":
a := auth.NewAuthenticator()
if err := a.Login(context.Background()); err != nil {
fmt.Fprintln(os.Stderr, color.RedString("Login attempt failed. Please try again"))
return err
}

fmt.Println(color.GreenString("Login successful"))
case "logout":
panic("To be implemented")
default:
Expand Down
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ module github.com/handofgod94/gh-jira-changelog
go 1.20

require (
github.com/fatih/color v1.15.0
github.com/go-resty/resty/v2 v2.10.0
github.com/mitchellh/go-homedir v1.1.0
github.com/qmuntal/stateless v1.6.7
github.com/samber/lo v1.38.1
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
Expand All @@ -21,6 +23,8 @@ require (
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
Expand Down Expand Up @@ -142,6 +144,13 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
Expand Down Expand Up @@ -340,6 +349,7 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
43 changes: 35 additions & 8 deletions pkg/jira_changelog/jira/auth/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"net/http"
"time"

"github.com/fatih/color"
"github.com/qmuntal/stateless"
"github.com/skratchdot/open-golang/open"
"golang.org/x/exp/slog"
Expand All @@ -20,20 +21,23 @@ const (
stateInit = "Init"
stateTokenObtained = "TokenObtained"
stateAccessibleResourcesObtained = "AccessibleResourcesObtained"
stateJiraConfigSaved = "JiraConfigSaved"

// events
triggerCodeExchange = "CodeExchange"
triggerFetchAccessibleResources = "FetchAccessibleResources"
triggerPersistResourcesInfo = "PersistResourcesInfo"
)

type oauthAuthenticator struct {
loginWorkflow *stateless.StateMachine

oauthToken *oauth2.Token
conf *oauth2.Config
verifier string
ctx context.Context
callback chan *oauth2.Token
oauthToken *oauth2.Token
conf *oauth2.Config
verifier string
ctx context.Context
callback chan *oauth2.Token
allowedResourcesResponse []byte
}

func NewAuthenticator() *oauthAuthenticator {
Expand Down Expand Up @@ -62,7 +66,11 @@ func NewAuthenticator() *oauthAuthenticator {
a.isTokenValid, a.isOauthContextPresent)

loginWorkflow.Configure(stateAccessibleResourcesObtained).
OnEntry(a.fetchAccessibleResources)
OnEntry(a.fetchAccessibleResources).
Permit(triggerPersistResourcesInfo, stateJiraConfigSaved)

loginWorkflow.Configure(stateJiraConfigSaved).
OnEntry(a.saveJiraConfig)

a.loginWorkflow = loginWorkflow

Expand All @@ -77,6 +85,10 @@ func (a *oauthAuthenticator) Login(ctx context.Context) error {
if err := a.loginWorkflow.FireCtx(ctx, triggerFetchAccessibleResources); err != nil {
return err
}

if err := a.loginWorkflow.FireCtx(ctx, triggerPersistResourcesInfo); err != nil {
return err
}
return nil
}

Expand All @@ -92,8 +104,8 @@ func (a *oauthAuthenticator) exchangeCode(ctx context.Context, args ...any) erro
oauth2.SetAuthURLParam("audience", "api.atlassian.com"),
)

slog.Info("You will now be taken to browser for authentication.")
slog.Info("Please grant permission to access jira issues")
fmt.Println(color.CyanString("You will now be taken to browser for authentication."))
fmt.Println(color.CyanString("Please grant permission to access jira issues"))

time.Sleep(1 * time.Second)
open.Run(url)
Expand Down Expand Up @@ -132,6 +144,21 @@ func (a *oauthAuthenticator) fetchAccessibleResources(ctx context.Context, args
}
slog.Debug("Retrieved accessible resources successfully", "resources", string(accessibleResources))

a.allowedResourcesResponse = accessibleResources

return nil
}

func (a *oauthAuthenticator) saveJiraConfig(ctx context.Context, args ...any) error {
resources, err := parseResources(a.allowedResourcesResponse)
if err != nil {
return err
}

err = resources[0].Save()
if err != nil {
return err
}
return nil
}

Expand Down
72 changes: 72 additions & 0 deletions pkg/jira_changelog/jira/auth/resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package auth

import (
"encoding/json"
"fmt"
"os"
"path"

"github.com/mitchellh/go-homedir"
)

const ResourcesFile = "resources.json"

type Resource struct {
CloudId string `json:"id"`
Name string `json:"name"`
BaseURL string `json:"url"`
Scopes []string `json:"scopes"`
}

func (r Resource) Save() error {

confdir, err := DefaultConfDir()
if err != nil {
return fmt.Errorf("failed to generate config file %w", err)
}

if _, err := os.Stat(confdir); os.IsNotExist(err) {
err = os.Mkdir(confdir, os.ModeDir|0755)
if err != nil {
return err
}
}

filepath := path.Join(confdir, ResourcesFile)
f, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY, os.ModePerm)
if err != nil {
return err
}
defer f.Close()

enc := json.NewEncoder(f)
err = enc.Encode(r)
if err != nil {
return fmt.Errorf("failed to encode resources to json. %w", err)
}

return nil
}

func parseResources(raw []byte) ([]Resource, error) {
result := make([]Resource, 0)

if err := json.Unmarshal(raw, &result); err != nil {
return []Resource{}, nil
}

return result, nil
}

func DefaultConfDir() (res string, err error) {
filepath := path.Join("gh-jira-changelog")
res = os.Getenv("XDG_CONFIG_HOME")
if res == "" {
res, err = homedir.Dir()
if err != nil {
return
}
}

return path.Join(res, filepath), nil
}
64 changes: 64 additions & 0 deletions pkg/jira_changelog/jira/auth/resource_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package auth

import (
"os"
"path"
"testing"

"github.com/mitchellh/go-homedir"
"github.com/stretchr/testify/assert"
)

func TestDefaultDir_When_XDG_CONFIG_HOME_isSet(t *testing.T) {
os.Setenv("XDG_CONFIG_HOME", "~/foobar")
got, err := DefaultConfDir()

assert.NoError(t, err)
assert.Equal(t, "~/foobar/gh-jira-changelog", got)
}

func TestDefaultDir_When_XDG_CONFIG_HOME_isNotSet(t *testing.T) {
os.Unsetenv("XDG_CONFIG_HOME")

homeDir, _ := homedir.Dir()
expected := path.Join(homeDir, "gh-jira-changelog")

got, err := DefaultConfDir()

assert.NoError(t, err)
assert.Equal(t, expected, got)
}

func TestParseResources(t *testing.T) {
raw := `[
{
"id": "1324a887-45db-1bf4-1e99-ef0ff456d421",
"name": "Site name",
"url": "https://your-domain.atlassian.net",
"scopes": [
"write:jira-work",
"read:jira-user",
"manage:jira-configuration"
],
"avatarUrl": "https:\/\/site-admin-avatar-cdn.prod.public.atl-paas.net\/avatars\/240\/flag.png"
}
]`

got, err := parseResources([]byte(raw))

want := []Resource{
{
CloudId: "1324a887-45db-1bf4-1e99-ef0ff456d421",
Name: "Site name",
BaseURL: "https://your-domain.atlassian.net",
Scopes: []string{
"write:jira-work",
"read:jira-user",
"manage:jira-configuration",
},
},
}

assert.NoError(t, err)
assert.Equal(t, want, got)
}
1 change: 1 addition & 0 deletions pkg/jira_changelog/jira/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type client struct {

func (c *client) setupClient() {
c.httpClient = resty.New()
c.httpClient.SetBaseURL(c.config.BaseURL)
c.httpClient.SetBasicAuth(c.config.User, c.config.ApiToken)
c.httpClient.SetTimeout(5 * time.Second)
}
Expand Down

0 comments on commit 71b8957

Please sign in to comment.