Compare commits

...
Author SHA1 Message Date
Vitor Rodrigo Vezani a1e1c4c272 Merge branch 'master' into FWI-4156/vv/auth-mngmt 2023-05-26 11:12:19 -03:00
Vitor Vezani 1de16af979 fix logrus directive usage 2023-05-22 12:42:49 -03:00
Vitor Vezani b63b3e704c remove comments 2023-05-19 18:02:39 -03:00
Vitor Vezani 087b2cc6a4 make insights URL for login configurable 2023-05-19 13:20:49 -03:00
Vitor Vezani 6e084c9ccf fix login flow 2023-05-19 11:49:56 -03:00
Vitor Vezani e3fcdfd8bf improve login flow 2023-05-18 13:51:22 -03:00
Vitor Vezani 9cb384d638 improve server port management 2023-05-17 12:28:54 -03:00
Vitor Vezani 00d66d2dae add user to login 2023-05-16 11:24:39 -03:00
Vitor Vezani a5ada4303b implement status command 2023-05-16 10:38:05 -03:00
Vitor Vezani 5c1c68a74a implement token and status print 2023-05-15 18:29:43 -03:00
Vitor Vezani bf889a280b improve code 2023-05-15 18:21:14 -03:00
Vitor Vezani b837ce23e3 add logout functionality 2023-05-15 18:07:03 -03:00
Vitor Vezani 9b2db39668 add login flow 2023-05-15 15:56:54 -03:00
9 changed files with 567 additions and 0 deletions
+85
View File
@@ -0,0 +1,85 @@
package cmd
import (
"github.com/fairwindsops/polaris/pkg/auth"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
var insightsURL string
func init() {
loginCmd.Flags().StringVar(&insightsURL, "insights-host", "https://insights.fairwinds.com", "insights host URL")
rootCmd.AddCommand(authCmd)
authCmd.AddCommand(loginCmd)
authCmd.AddCommand(logoutCmd)
authCmd.AddCommand(statusCmd)
authCmd.AddCommand(tokenCmd)
}
var authCmd = &cobra.Command{
Use: "auth",
Short: "Authenticate polaris with Fairwinds Insights",
Long: `Authenticate polaris with Fairwinds Insights so better experience`,
}
var loginCmd = &cobra.Command{
Use: "login",
Short: "Authenticate polaris with Fairwinds Insights.",
Long: `Authenticate polaris with Fairwinds Insights.`,
Run: func(cmd *cobra.Command, args []string) {
err := auth.HandleLogin(insightsURL)
if err != nil {
logrus.Fatal(err)
}
},
PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
return nil
},
}
var logoutCmd = &cobra.Command{
Use: "logout",
Short: "Log out of a Fairwinds Insights.",
Long: `Log out of a Fairwinds Insights.`,
Run: func(cmd *cobra.Command, args []string) {
err := auth.HandleLogout()
if err != nil {
logrus.Fatal(err)
}
},
PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
return nil
},
}
var statusCmd = &cobra.Command{
Use: "status",
Short: "View authentication status.",
Long: `View authentication status.`,
Run: func(cmd *cobra.Command, args []string) {
err := auth.PrintStatus()
if err != nil {
logrus.Fatalf("printing status")
}
},
PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
return nil
},
}
var tokenCmd = &cobra.Command{
Use: "token",
Short: "Print the auth token gh is configured to use.",
Long: `Print the auth token gh is configured to use.`,
Run: func(cmd *cobra.Command, args []string) {
err := auth.PrintToken()
if err != nil {
logrus.Fatalf("printing token: %v", err)
}
},
PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
return nil
},
}
+3
View File
@@ -23,6 +23,7 @@ require (
)
require (
github.com/AlecAivazis/survey/v2 v2.3.6
github.com/pkg/errors v0.9.1
gomodules.xyz/jsonpatch/v2 v2.2.0
)
@@ -53,6 +54,7 @@ require (
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/karrick/godirwalk v1.17.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/markbates/errx v1.1.0 // indirect
github.com/markbates/oncer v1.0.0 // indirect
@@ -60,6 +62,7 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+17
View File
@@ -37,8 +37,12 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/AlecAivazis/survey/v2 v2.3.6 h1:NvTuVHISgTHEHeBFqt6BHOe4Ny/NwGZr7w+F8S9ziyw=
github.com/AlecAivazis/survey/v2 v2.3.6/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@@ -66,6 +70,8 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -213,6 +219,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
@@ -233,6 +241,8 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
github.com/karrick/godirwalk v1.17.0 h1:b4kY7nqDdioR/6qnbHQyDvmA17u5G1cZ6J+CZXwSWoI=
github.com/karrick/godirwalk v1.17.0/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
@@ -254,14 +264,18 @@ github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2
github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI=
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
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.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
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/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@@ -487,6 +501,7 @@ golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -528,12 +543,14 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/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 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
+36
View File
@@ -0,0 +1,36 @@
package auth
import (
"io"
"os"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
)
var userHomeDir string
var polarisHostsFilepath string
func init() {
var err error
userHomeDir, err = os.UserHomeDir()
if err != nil {
logrus.Fatalf("reading user home dir: %v", err)
}
polarisHostsFilepath = userHomeDir + "/.config/polaris/hosts.yaml"
}
func readPolarisHostsFile() (map[string]Host, error) {
f, err := os.Open(polarisHostsFilepath)
if err != nil {
return nil, err
}
b, err := io.ReadAll(f)
if err != nil {
return nil, err
}
content := map[string]Host{}
err = yaml.Unmarshal(b, &content)
return content, err
}
+45
View File
@@ -0,0 +1,45 @@
package auth
import (
"errors"
"fmt"
"os"
"os/exec"
"runtime"
"strings"
"github.com/sirupsen/logrus"
)
// openBrowser opens up the provided URL in a browser
func openBrowser(url string) error {
var cmd *exec.Cmd
switch runtime.GOOS {
case "openbsd":
fallthrough
case "linux":
cmd = exec.Command("xdg-open", url)
case "darwin":
cmd = exec.Command("open", url)
case "windows":
r := strings.NewReplacer("&", "^&")
cmd = exec.Command("cmd", "/c", "start", r.Replace(url))
}
if cmd != nil {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()
if err != nil {
logrus.Printf("Failed to open browser due to error %v", err)
return fmt.Errorf("Failed to open browser: " + err.Error())
}
err = cmd.Wait()
if err != nil {
logrus.Printf("Failed to wait for open browser command to finish due to error %v", err)
return fmt.Errorf("Failed to wait for open browser command to finish: " + err.Error())
}
return nil
} else {
return errors.New("unsupported platform")
}
}
+277
View File
@@ -0,0 +1,277 @@
package auth
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
)
const loginPath = "/auth/login"
const registerPath = "/auth/register"
const (
loginUsingBrowser = "Login with a web browser"
pasteAnAuthenticationToken = "Paste an authentication token"
)
type paramsOrError struct {
token string
user string
organization string
err error
}
type Host struct {
Token string `yaml:"token"`
User string `yaml:"user"`
Organization string `yaml:"organization"`
}
var paramsOrErrorChan = make(chan paramsOrError)
func HandleLogin(insightsURL string) error {
if _, err := os.Stat(polarisHostsFilepath); err == nil {
content, err := readPolarisHostsFile()
if err != nil {
return fmt.Errorf("reading polaris hosts file: %w", err)
}
if len(content) > 0 {
var reAuthenticate bool
err = survey.AskOne(&survey.Confirm{Message: fmt.Sprintf("You're already logged into %s. Do you want to re-authenticate?", insightsURL)}, &reAuthenticate)
if err != nil {
return fmt.Errorf("prompting re-authenticate: %w", err)
}
if !reAuthenticate {
// bail-out
return nil
}
}
}
selection := &survey.Select{
Message: "How would you like to authenticate Polaris?",
Options: []string{loginUsingBrowser, pasteAnAuthenticationToken},
Default: loginUsingBrowser,
}
var answer string
err := survey.AskOne(selection, &answer)
if err != nil {
return fmt.Errorf("asking how to authenticate: %w", err)
}
var user, token, organization string
if answer == loginUsingBrowser {
listener, err := net.Listen("tcp", ":0")
if err != nil {
panic(err)
}
localServerPort := listener.Addr().(*net.TCPAddr).Port
err = openBrowser(fmt.Sprintf(insightsURL + registerPath + "?source=polaris&callbackUrl=" + fmt.Sprintf("http://localhost:%d/auth/login/callback", localServerPort)))
if err != nil {
logrus.Fatal(err)
}
var router *mux.Router
go func() {
router = mux.NewRouter()
router.HandleFunc("/auth/login/callback", callbackHandler(insightsURL, localServerPort))
if err := http.Serve(listener, router); err != nil {
paramsOrErrorChan <- paramsOrError{err: fmt.Errorf("starting the local http server: %w", err)}
}
}()
// wait the browser to callback the local server
paramOrError := <-paramsOrErrorChan
if paramOrError.err != nil {
return paramOrError.err
}
user = paramOrError.user
organization = paramOrError.organization
token = paramOrError.token
} else {
var answer string
var bot Bot
err := survey.AskOne(&survey.Password{Message: "Paste your authentication token:"}, &answer, survey.WithValidator(validateToken(insightsURL, &bot)))
if err != nil {
return fmt.Errorf("asking how to authenticate: %w", err)
}
token = answer
user = bot.Name
organization = bot.Organization
}
polarisCfgDir := filepath.Join(userHomeDir, ".config", "polaris")
err = os.MkdirAll(polarisCfgDir, os.ModePerm)
if err != nil {
return fmt.Errorf("creating polaris config dir: %w", err)
}
f, err := os.Create(polarisHostsFilepath)
if err != nil {
return fmt.Errorf("opening user polaris hosts file: %w", err)
}
defer func() {
if err := f.Close(); err != nil {
logrus.Fatalf("closing user polaris hosts file: %v", err)
}
}()
content := map[string]Host{insightsURL: {Token: token, User: user, Organization: organization}}
b, err := yaml.Marshal(content)
if err != nil {
return fmt.Errorf("marshalling yaml data: %w", err)
}
_, err = f.Write(b)
if err != nil {
return fmt.Errorf("writing data to file: %w", err)
}
logrus.Debugf("hosts file has been saved")
fmt.Println("✓ Authentication complete.")
fmt.Printf("✓ Logged in organization %s as %s.\n", organization, user)
return nil
}
func fetchAuthToken(insightsURL, organization, code string) (string, error) {
authTokenURL := fmt.Sprintf("%s/v0/organizations/%s/auth/token", insightsURL, organization)
body := map[string]any{"grantType": "authorization_code", "code": code}
b, err := json.Marshal(body)
if err != nil {
return "", err
}
r, err := http.NewRequest("POST", authTokenURL, bytes.NewBuffer(b))
if err != nil {
return "", err
}
r.Header.Add("Content-Type", "application/json")
res, err := http.DefaultClient.Do(r)
if err != nil {
return "", err
}
defer res.Body.Close()
if res.StatusCode < 200 || res.StatusCode >= 400 {
return "", fmt.Errorf("expected 200 (OK) - received %d", res.StatusCode)
}
var rBody map[string]any
err = json.NewDecoder(res.Body).Decode(&rBody)
if err != nil {
return "", err
}
token, ok := rBody["accessToken"].(string)
if !ok {
return "", fmt.Errorf("unable to parse accessToken from response body: %v", rBody)
}
return token, nil
}
func callbackHandler(insightsURL string, localServerPort int) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
// checks for error in the params
errMsg := r.URL.Query().Get("error")
if len(errMsg) > 0 {
errDescriptionMsg := r.URL.Query().Get("error_description")
fmt.Fprintf(w, "unable to perform integration: %s - %s", errMsg, errDescriptionMsg)
paramsOrErrorChan <- paramsOrError{err: fmt.Errorf("%s - %s", errMsg, errDescriptionMsg)}
return
}
var err error
code := r.URL.Query().Get("code")
if len(code) == 0 {
err = errors.New("code query param is required in callback")
}
user := r.URL.Query().Get("user")
if len(user) == 0 {
err = errors.New("user query param is required in callback")
}
organization := r.URL.Query().Get("organization")
if len(organization) == 0 {
err = errors.New("organization query param is required in callback")
}
token, err := fetchAuthToken(insightsURL, organization, code)
if err != nil {
err = fmt.Errorf("fetching auth token: %w", err)
}
if err != nil {
fmt.Fprintf(w, "unable to perform integration: %v", err)
paramsOrErrorChan <- paramsOrError{err: err}
return
}
fmt.Fprint(w, "integration finished successfully, you can safely close this tab now.")
paramsOrErrorChan <- paramsOrError{token: token, user: user, organization: organization}
return
}
}
func validateToken(insightsURL string, bot *Bot) func(args any) error {
return func(args any) error {
token, ok := args.(string)
if !ok {
return errors.New("casting token to string")
}
if len(strings.TrimSpace(token)) <= 0 {
return errors.New("token is required")
}
return fetchOrganizationBot(insightsURL, token, bot)
}
}
type Bot struct {
ID int
Organization string
Name string
Role string
AuthToken string
CreatedAt time.Time
}
func fetchOrganizationBot(insightsURL, authToken string, bot *Bot) error {
authTokenURL := fmt.Sprintf("%s/v0/bots/from-request", insightsURL)
r, err := http.NewRequest("GET", authTokenURL, nil)
if err != nil {
return err
}
r.Header.Add("Content-Type", "application/json")
r.Header.Add("Authorization", "Bearer "+authToken)
res, err := http.DefaultClient.Do(r)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode < 200 || res.StatusCode >= 400 {
return fmt.Errorf("expected 200 (OK) - received %d", res.StatusCode)
}
err = json.NewDecoder(res.Body).Decode(bot)
if err != nil {
return err
}
return nil
}
+49
View File
@@ -0,0 +1,49 @@
package auth
import (
"fmt"
"os"
"github.com/sirupsen/logrus"
)
func HandleLogout() error {
ok, err := performLogout()
if err != nil {
return fmt.Errorf("performing logout: %v", err)
}
if ok {
fmt.Println("✓ Logged out of Fairwinds Insights")
} else {
fmt.Println("not logged in to Fairwinds Insights")
}
return nil
}
func performLogout() (bool, error) {
if _, err := os.Stat(polarisHostsFilepath); err == nil {
content, err := readPolarisHostsFile()
if err != nil {
return false, nil
}
if len(content) > 0 {
f, err := os.Create(polarisHostsFilepath)
if err != nil {
return false, nil
}
defer func() {
if err := f.Close(); err != nil {
logrus.Fatalf("closing user polaris hosts file: %v", err)
}
}()
_, err = f.Write([]byte("{}"))
if err != nil {
return false, fmt.Errorf("writing data to file: %w", err)
}
return true, nil
}
}
return false, nil
}
+35
View File
@@ -0,0 +1,35 @@
package auth
import (
"fmt"
"strings"
)
func PrintStatus() error {
if content, err := readPolarisHostsFile(); err == nil {
if len(content) > 0 {
for k, h := range content {
if len(content) == 1 {
fmt.Printf("✓ Logged in to %s as %s\n", k, h.User)
fmt.Printf("✓ Token: %s\n", hideToken(h.Token, 3))
}
}
return nil
}
}
fmt.Println("You are not logged into Fairwinds Insights. Run polaris auth login to authenticate.")
return nil
}
func hideToken(token string, hideAfter int) string {
var i int
return strings.Map(func(r rune) rune {
defer func() {
i++
}()
if i > hideAfter {
return []rune("*")[0]
}
return r
}, token)
}
+20
View File
@@ -0,0 +1,20 @@
package auth
import "fmt"
func PrintToken() error {
if content, err := readPolarisHostsFile(); err == nil {
if len(content) > 0 {
for k, h := range content {
if len(content) == 1 {
fmt.Println(h.Token)
} else {
fmt.Printf("%s: %s\n", k, h.Token)
}
}
return nil
}
}
fmt.Println("no oauth token")
return nil
}