diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 444937c3..efb88c91 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -20,10 +20,8 @@ type IAuthenticator interface { } type Authenticator struct { - ClientID string - Scopes []string - Client client.IDebClient SecretClient ISecretClient + OAuthConfig *oauth2.Config } type ISecretClient interface { @@ -50,12 +48,19 @@ func (dsc DebrickedSecretClient) Delete(service string) error { func NewDebrickedAuthenticator(client client.IDebClient) Authenticator { return Authenticator{ - ClientID: "01919462-7d6e-78e8-aa24-ba779213c90f", - Scopes: []string{"select", "profile", "basicRepo"}, - Client: client, SecretClient: DebrickedSecretClient{ User: "DebrickedCLI", }, + OAuthConfig: &oauth2.Config{ + ClientID: "01919462-7d6e-78e8-aa24-ba779213c90f", + ClientSecret: "", + Endpoint: oauth2.Endpoint{ + AuthURL: client.Host() + "/app/oauth/authorize", + TokenURL: client.Host() + "/app/oauth/token", + }, + RedirectURL: "http://localhost:9096/callback", + Scopes: []string{"select", "profile", "basicRepo"}, + }, } } @@ -84,49 +89,19 @@ func (a Authenticator) Token() (*oauth2.Token, error) { }, nil } -func (a Authenticator) Authenticate() error { - // Set up OAuth2 configuration - config := &oauth2.Config{ - ClientID: a.ClientID, - ClientSecret: "", - Endpoint: oauth2.Endpoint{ - AuthURL: a.Client.Host() + "/app/oauth/authorize", - TokenURL: a.Client.Host() + "/app/oauth/token", - }, - RedirectURL: "http://localhost:9096/callback", - Scopes: a.Scopes, - } - - // Create a random state - state := oauth2.GenerateVerifier() - codeVerifier := oauth2.GenerateVerifier() - - // Generate the authorization URL - authURL := config.AuthCodeURL( - state, - oauth2.S256ChallengeOption(codeVerifier), - ) - - // Start a temporary HTTP server to handle the callback +func (a Authenticator) callback(state string) string { code := make(chan string) defer close(code) - server := &http.Server{Addr: ":9096"} - // Start the server in a goroutine + server := &http.Server{Addr: ":9096"} // Start the server in a goroutine go func() { if err := server.ListenAndServe(); err != http.ErrServerClosed { log.Fatalf("HTTP server error: %v", err) } }() - // Ensure the server is shut down when we're done - defer server.Shutdown(context.Background()) - - // Open the browser for the user to log in - err := openBrowser(authURL) - if err != nil { - log.Fatal("Could not open browser:", err) - } - + defer server.Shutdown( + context.Background(), + ) // Ensure the server is shut down when we're done http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) { if r.URL.Query().Get("state") != state { http.Error(w, "Invalid state", http.StatusBadRequest) @@ -136,19 +111,41 @@ func (a Authenticator) Authenticate() error { code <- r.URL.Query().Get("code") fmt.Fprintf(w, "Authentication successful! You can close this window now.") }) - // Wait for the authorization code - authCode := <-code + authCode := <-code // Wait for the authorization code - // Exchange the authorization code for a token - token, err := config.Exchange( + return authCode +} + +func (a Authenticator) exchange(authCode, codeVerifier string) (*oauth2.Token, error) { + return a.OAuthConfig.Exchange( context.Background(), authCode, - oauth2.SetAuthURLParam("client_id", a.ClientID), + oauth2.SetAuthURLParam("client_id", a.OAuthConfig.ClientID), oauth2.VerifierOption(codeVerifier), ) + +} + +func (a Authenticator) Authenticate() error { + state := oauth2.GenerateVerifier() + codeVerifier := oauth2.GenerateVerifier() + + authURL := a.OAuthConfig.AuthCodeURL( + state, + oauth2.S256ChallengeOption(codeVerifier), + ) + + err := openBrowser(authURL) + if err != nil { + log.Fatal("Could not open browser:", err) + } + + authCode := a.callback(state) + token, err := a.exchange(authCode, codeVerifier) if err != nil { return err } + a.SecretClient.Set("DebrickedRefreshToken", token.RefreshToken) a.SecretClient.Set("DebrickedAccessToken", token.AccessToken) return nil diff --git a/internal/auth/auth_test.go b/internal/auth/auth_test.go new file mode 100644 index 00000000..a2c74afc --- /dev/null +++ b/internal/auth/auth_test.go @@ -0,0 +1,13 @@ +package auth + +import ( + "testing" + + "github.com/debricked/cli/internal/client/testdata" + "github.com/stretchr/testify/assert" +) + +func TestNewGeneration(t *testing.T) { + res := NewDebrickedAuthenticator(testdata.NewDebClientMock()) + assert.NotNil(t, res) +} diff --git a/internal/cmd/auth/auth_test.go b/internal/cmd/auth/auth_test.go new file mode 100644 index 00000000..4cb4114a --- /dev/null +++ b/internal/cmd/auth/auth_test.go @@ -0,0 +1,24 @@ +package auth + +import ( + "testing" + + "github.com/debricked/cli/internal/auth" + "github.com/debricked/cli/internal/client" + "github.com/stretchr/testify/assert" +) + +func TestNewFilesCmd(t *testing.T) { + token := "token" + deb_client := client.NewDebClient(&token, nil) + authenticator := auth.NewDebrickedAuthenticator(deb_client) + cmd := NewAuthCmd(authenticator) + commands := cmd.Commands() + nbrOfCommands := 3 + assert.Lenf(t, commands, nbrOfCommands, "failed to assert that there were %d sub commands connected", nbrOfCommands) +} + +func TestPreRun(t *testing.T) { + cmd := NewAuthCmd(nil) + cmd.PreRun(cmd, nil) +} diff --git a/internal/cmd/auth/login/login.go b/internal/cmd/auth/login/login.go index 427a5204..e87689d7 100644 --- a/internal/cmd/auth/login/login.go +++ b/internal/cmd/auth/login/login.go @@ -18,13 +18,12 @@ func NewLoginCmd(authenticator auth.IAuthenticator) *cobra.Command { }, RunE: RunE(authenticator), } - return cmd } func RunE(a auth.IAuthenticator) func(_ *cobra.Command, args []string) error { return func(cmd *cobra.Command, _ []string) error { - _, err := a.Authenticate() + err := a.Authenticate() if err != nil { return err } diff --git a/internal/cmd/auth/token/token.go b/internal/cmd/auth/token/token.go index 68175e7a..c4140fba 100644 --- a/internal/cmd/auth/token/token.go +++ b/internal/cmd/auth/token/token.go @@ -1,7 +1,9 @@ package token import ( + "encoding/json" "fmt" + "github.com/debricked/cli/internal/auth" "github.com/fatih/color" @@ -9,6 +11,10 @@ import ( "github.com/spf13/viper" ) +var jsonFormat bool + +const JsonFlag = "json" + func NewTokenCmd(authenticator auth.IAuthenticator) *cobra.Command { cmd := &cobra.Command{ Use: "token", @@ -19,6 +25,18 @@ func NewTokenCmd(authenticator auth.IAuthenticator) *cobra.Command { }, RunE: RunE(authenticator), } + cmd.Flags().BoolVarP(&jsonFormat, JsonFlag, "j", false, `Print files in JSON format +Format: +[ + { + "access_token": , + "token_type": "jwt", + "refresh_token": , + "expiry": , + }, +] +`) + viper.MustBindEnv(JsonFlag) return cmd } @@ -29,11 +47,16 @@ func RunE(a auth.IAuthenticator) func(_ *cobra.Command, args []string) error { if err != nil { return err } - fmt.Printf( - "Refresh Token = %s\nAccess Token = %s\n", - color.BlueString(token.RefreshToken), - color.BlueString(token.AccessToken), - ) + if viper.GetBool(JsonFlag) { + jsonToken, _ := json.Marshal(token) + fmt.Println(string(jsonToken)) + } else { + fmt.Printf( + "Refresh Token = %s\nAccess Token = %s\n", + color.BlueString(token.RefreshToken), + color.BlueString(token.AccessToken), + ) + } return nil }