Skip to content

Commit

Permalink
[FEATURE] Add a new command to refresh the access token (#1668)
Browse files Browse the repository at this point in the history
Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
  • Loading branch information
Nexucis authored Dec 20, 2023
1 parent 57eeb12 commit 9802a05
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 21 deletions.
2 changes: 2 additions & 0 deletions cmd/percli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/perses/perses/internal/cli/cmd/login"
"github.com/perses/perses/internal/cli/cmd/migrate"
"github.com/perses/perses/internal/cli/cmd/project"
"github.com/perses/perses/internal/cli/cmd/refresh"
"github.com/perses/perses/internal/cli/cmd/remove"
"github.com/perses/perses/internal/cli/cmd/version"
"github.com/perses/perses/internal/cli/config"
Expand All @@ -48,6 +49,7 @@ func newRootCommand() *cobra.Command {
cmd.AddCommand(lint.NewCMD())
cmd.AddCommand(login.NewCMD())
cmd.AddCommand(migrate.NewCMD())
cmd.AddCommand(refresh.NewCMD())
cmd.AddCommand(project.NewCMD())
cmd.AddCommand(remove.NewCMD())
cmd.AddCommand(version.NewCMD())
Expand Down
29 changes: 16 additions & 13 deletions internal/cli/cmd/login/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,15 @@ func readPassword() (string, error) {

type option struct {
persesCMD.Option
writer io.Writer
url string
username string
password string
token string
insecureTLS bool
apiClient api.ClientInterface
restConfig perseshttp.RestConfigClient
writer io.Writer
url string
username string
password string
accessToken string
refreshToken string
insecureTLS bool
apiClient api.ClientInterface
restConfig perseshttp.RestConfigClient
}

func (o *option) Complete(args []string) error {
Expand Down Expand Up @@ -84,7 +85,7 @@ func (o *option) Validate() error {
if _, err := url.Parse(o.url); err != nil {
return err
}
if len(o.username) > 0 && len(o.token) > 0 {
if len(o.username) > 0 && len(o.accessToken) > 0 {
return fmt.Errorf("--token and --username are mutually exclusive")
}
return nil
Expand All @@ -95,17 +96,18 @@ func (o *option) Execute() error {
if err != nil {
return err
}
if cfg.Security.EnableAuth && len(o.token) == 0 {
if cfg.Security.EnableAuth && len(o.accessToken) == 0 {
if readErr := o.readAndSetCredentialInput(); readErr != nil {
return readErr
}
if authErr := o.authAndSetToken(); authErr != nil {
return authErr
}
}
o.restConfig.Token = o.token
o.restConfig.Token = o.accessToken
if writeErr := config.Write(&config.Config{
RestClientConfig: o.restConfig,
RefreshToken: o.refreshToken,
}); writeErr != nil {
return writeErr
}
Expand All @@ -121,7 +123,8 @@ func (o *option) authAndSetToken() error {
if err != nil {
return err
}
o.token = token.AccessToken
o.accessToken = token.AccessToken
o.refreshToken = token.RefreshToken
return nil
}

Expand Down Expand Up @@ -161,6 +164,6 @@ percli login https://perses.dev
cmd.Flags().BoolVar(&o.insecureTLS, "insecure-skip-tls-verify", o.insecureTLS, "If true the server's certificate will not be checked for validity. This will make your HTTPS connections insecure.")
cmd.Flags().StringVarP(&o.username, "username", "u", "", "Username used for the authentication.")
cmd.Flags().StringVarP(&o.password, "password", "p", "", "Password used for the authentication.")
cmd.Flags().StringVar(&o.token, "token", "", "Bearer token for authentication to the API server")
cmd.Flags().StringVar(&o.accessToken, "token", "", "Bearer token for authentication to the API server")
return cmd
}
4 changes: 2 additions & 2 deletions internal/cli/cmd/project/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ func (o *option) Validate() error {

func (o *option) Execute() error {
if len(o.projectName) == 0 {
// In that case we simply print the current project used.
// In that case, we simply print the current project used.
fmt.Printf("Using project %q on server %q\n", config.Global.Project, config.Global.RestClientConfig.URL)
return nil
}
// in case the project is provided we should verify if it exists first
// in case the project is provided, we should verify if it exists first
_, err := o.apiClient.V1().Project().Get(o.projectName)
if err != nil {
if errors.Is(err, perseshttp.RequestNotFoundError) {
Expand Down
77 changes: 77 additions & 0 deletions internal/cli/cmd/refresh/refresh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2023 The Perses Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package refresh

import (
"fmt"
"io"

persesCMD "github.com/perses/perses/internal/cli/cmd"
"github.com/perses/perses/internal/cli/config"
"github.com/perses/perses/internal/cli/output"
"github.com/perses/perses/pkg/client/api"
"github.com/spf13/cobra"
)

type option struct {
persesCMD.Option
writer io.Writer
apiClient api.ClientInterface
}

func (o *option) Complete(args []string) error {
if len(args) > 0 {
return fmt.Errorf("no args are supported by the command 'apply'")
}
apiClient, err := config.Global.GetAPIClient()
if err != nil {
return err
}
o.apiClient = apiClient
return nil
}

func (o *option) Validate() error {
if len(config.Global.RefreshToken) == 0 {
return fmt.Errorf("refresh_token doesn't exist in the config, please use the command login to get one")
}
return nil
}

func (o *option) Execute() error {
response, err := o.apiClient.Auth().Refresh(config.Global.RefreshToken)
if err != nil {
return err
}
if writeErr := config.SetAccessToken(response.AccessToken); writeErr != nil {
return writeErr
}
return output.HandleString(o.writer, "access token has been refreshed")
}

func (o *option) SetWriter(writer io.Writer) {
o.writer = writer
}

func NewCMD() *cobra.Command {
o := &option{}
cmd := &cobra.Command{
Use: "refresh",
Short: "refresh the access token when it expires",
RunE: func(cmd *cobra.Command, args []string) error {
return persesCMD.Run(o, cmd, args)
},
}
return cmd
}
12 changes: 11 additions & 1 deletion internal/cli/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ func Init(configPath string) {

type Config struct {
RestClientConfig perseshttp.RestConfigClient `json:"rest_client_config"`
Project string `json:"project"`
Project string `json:"project,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
filePath string
apiClient api.ClientInterface
}
Expand Down Expand Up @@ -118,6 +119,12 @@ func SetProject(project string) error {
})
}

func SetAccessToken(token string) error {
return Write(&Config{
RestClientConfig: perseshttp.RestConfigClient{Token: token},
})
}

// Write writes the configuration file in the path {USER_HOME}/.perses/config
// if the directory doesn't exist, the function will create it
func Write(config *Config) error {
Expand Down Expand Up @@ -151,6 +158,9 @@ func Write(config *Config) error {
if len(config.Project) > 0 {
previousConf.Project = config.Project
}
if len(config.RefreshToken) > 0 {
previousConf.RefreshToken = config.RefreshToken
}
}
} else {
previousConf = config
Expand Down
19 changes: 14 additions & 5 deletions pkg/client/api/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
package api

import (
"fmt"

"github.com/perses/perses/pkg/client/perseshttp"
"github.com/perses/perses/pkg/model/api"
)
Expand All @@ -23,6 +25,7 @@ const authResource = "auth"
// AuthInterface has methods to work with Auth resource
type AuthInterface interface {
Login(user, password string) (*api.AuthResponse, error)
Refresh(refreshToken string) (*api.AuthResponse, error)
}

func newAuth(client *perseshttp.RESTClient) AuthInterface {
Expand All @@ -42,16 +45,22 @@ func (c *auth) Login(user string, password string) (*api.AuthResponse, error) {
}
result := &api.AuthResponse{}

err := c.client.Post().
return result, c.client.Post().
APIVersion("").
Resource(authResource).
Body(body).
Do().
Object(result)
}

if err != nil {
return nil, err
}
func (c *auth) Refresh(refreshToken string) (*api.AuthResponse, error) {
body := &api.RefreshRequest{RefreshToken: refreshToken}
result := &api.AuthResponse{}

return result, nil
return result, c.client.Post().
APIVersion("").
Resource(fmt.Sprintf("%s/refresh", authResource)).
Body(body).
Do().
Object(result)
}

0 comments on commit 9802a05

Please sign in to comment.