From 283b0631da2f68fcbd4fa7123e06f6d26ca65076 Mon Sep 17 00:00:00 2001 From: Jules Casteran Date: Fri, 16 Aug 2024 11:41:57 +0200 Subject: [PATCH 1/6] feat: add login command --- internal/namespaces/get_commands.go | 2 + internal/namespaces/init/init.go | 18 ++- internal/namespaces/login/login.go | 142 ++++++++++++++++++ .../namespaces/login/webcallback/options.go | 9 ++ .../login/webcallback/webcallback.go | 103 +++++++++++++ 5 files changed, 267 insertions(+), 7 deletions(-) create mode 100644 internal/namespaces/login/login.go create mode 100644 internal/namespaces/login/webcallback/options.go create mode 100644 internal/namespaces/login/webcallback/webcallback.go diff --git a/internal/namespaces/get_commands.go b/internal/namespaces/get_commands.go index 0dccadbcd5..9a29091983 100644 --- a/internal/namespaces/get_commands.go +++ b/internal/namespaces/get_commands.go @@ -34,6 +34,7 @@ import ( "github.com/scaleway/scaleway-cli/v2/internal/namespaces/k8s/v1" keymanager "github.com/scaleway/scaleway-cli/v2/internal/namespaces/key_manager/v1alpha1" "github.com/scaleway/scaleway-cli/v2/internal/namespaces/lb/v1" + "github.com/scaleway/scaleway-cli/v2/internal/namespaces/login" "github.com/scaleway/scaleway-cli/v2/internal/namespaces/marketplace/v2" mnq "github.com/scaleway/scaleway-cli/v2/internal/namespaces/mnq/v1beta1" "github.com/scaleway/scaleway-cli/v2/internal/namespaces/object/v1" @@ -104,6 +105,7 @@ func GetCommands() *core.Commands { jobs.GetCommands(), serverless_sqldb.GetCommands(), edgeservices.GetCommands(), + login.GetCommands(), ) if beta { diff --git a/internal/namespaces/init/init.go b/internal/namespaces/init/init.go index a554a48ec0..745337790a 100644 --- a/internal/namespaces/init/init.go +++ b/internal/namespaces/init/init.go @@ -54,10 +54,10 @@ See below the schema `scw init` follows to ask for default config: */ func GetCommands() *core.Commands { - return core.NewCommands(initCommand()) + return core.NewCommands(InitCommand()) } -type initArgs struct { +type InitArgs struct { AccessKey string SecretKey string ProjectID string @@ -70,7 +70,7 @@ type initArgs struct { InstallAutocomplete *bool } -func initCommand() *core.Command { +func InitCommand() *core.Command { return &core.Command{ Groups: []string{"config"}, Short: `Initialize the config`, @@ -83,7 +83,7 @@ Default path for configuration file is based on the following priority order: - $USERPROFILE/.config/scw/config.yaml`, Namespace: "init", AllowAnonymousClient: true, - ArgsType: reflect.TypeOf(initArgs{}), + ArgsType: reflect.TypeOf(InitArgs{}), ArgSpecs: core.ArgSpecs{ { Name: "secret-key", @@ -126,9 +126,13 @@ Default path for configuration file is based on the following priority order: Short: "Config management help", Command: "scw config", }, + { + Short: "Login through a web page", + Command: "scw login", + }, }, Run: func(ctx context.Context, argsI interface{}) (i interface{}, e error) { - args := argsI.(*initArgs) + args := argsI.(*InitArgs) profileName := core.ExtractProfileName(ctx) configPath := core.ExtractConfigPath(ctx) @@ -243,7 +247,7 @@ Default path for configuration file is based on the following priority order: successDetails := []string(nil) // Install autocomplete - if *args.InstallAutocomplete { + if args.InstallAutocomplete != nil && *args.InstallAutocomplete { _, _ = interactive.Println() _, err := autocomplete.InstallCommandRun(ctx, &autocomplete.InstallArgs{ Basename: "scw", @@ -254,7 +258,7 @@ Default path for configuration file is based on the following priority order: } // Init SSH Key - if *args.WithSSHKey { + if args.WithSSHKey != nil && *args.WithSSHKey { _, _ = interactive.Println() _, err := iamcommands.InitWithSSHKeyRun(ctx, nil) if err != nil { diff --git a/internal/namespaces/login/login.go b/internal/namespaces/login/login.go new file mode 100644 index 0000000000..d30245e3e4 --- /dev/null +++ b/internal/namespaces/login/login.go @@ -0,0 +1,142 @@ +package login + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "reflect" + "time" + + "github.com/scaleway/scaleway-cli/v2/internal/core" + initCommand "github.com/scaleway/scaleway-cli/v2/internal/namespaces/init" + "github.com/scaleway/scaleway-cli/v2/internal/namespaces/login/webcallback" + iam "github.com/scaleway/scaleway-sdk-go/api/iam/v1alpha1" + "github.com/scaleway/scaleway-sdk-go/logger" + "github.com/scaleway/scaleway-sdk-go/scw" + "github.com/skratchdot/open-golang/open" +) + +func GetCommands() *core.Commands { + return core.NewCommands(loginCommand()) +} + +type loginArgs struct { + Port int `json:"port"` +} + +func loginCommand() *core.Command { + return &core.Command{ + Groups: []string{"config"}, + Short: `Login to scaleway`, + Long: `Start an interactive connection to scaleway to initialize the active profile of the config +A webpage will open while the CLI will wait for a response. +Once you connected to Scaleway, the profile should be configured. +`, + Namespace: "login", + AllowAnonymousClient: true, + ArgsType: reflect.TypeOf(loginArgs{}), + ArgSpecs: core.ArgSpecs{ + { + Name: "port", + Short: "The port number used to wait for browser's response", + }, + }, + SeeAlsos: []*core.SeeAlso{ + { + Short: "Init profile manually", + Command: "scw init", + }, + }, + Run: func(ctx context.Context, argsI interface{}) (interface{}, error) { + args := argsI.(*loginArgs) + + opts := []webcallback.Options(nil) + if args.Port > 0 { + opts = append(opts, webcallback.WithPort(args.Port)) + } + + wb := webcallback.New(opts...) + err := wb.Start(ctx) + if err != nil { + return nil, err + } + + callbackURL := fmt.Sprintf("http://localhost:%d/callback", wb.Port()) + err = open.Start("https://account.scaleway.com/authenticate?redirectToUrl=" + callbackURL) + if err != nil { + logger.Warningf("Failed to open web url, you may not have a default browser configured") + logger.Warningf(fmt.Sprintf("You can open it: %s", callbackURL)) + } + + fmt.Println("waiting for callback...") + token, err := wb.Wait(ctx) + if err != nil { + return nil, err + } + + rawToken, err := base64.StdEncoding.DecodeString(token) + if err != nil { + return nil, err + } + + tt := Token{} + + err = json.Unmarshal(rawToken, &tt) + if err != nil { + return nil, err + } + + client, err := scw.NewClient(scw.WithJWT(tt.Token)) + if err != nil { + return nil, err + } + + api := iam.NewAPI(client) + apiKey, err := api.CreateAPIKey(&iam.CreateAPIKeyRequest{ + UserID: &tt.Jwt.AudienceID, + Description: "Generated by the Scaleway CLI", + }) + if err != nil { + return nil, err + } + + resp, err := initCommand.InitCommand().Run(ctx, &initCommand.InitArgs{ + AccessKey: apiKey.AccessKey, + SecretKey: *apiKey.SecretKey, + ProjectID: apiKey.DefaultProjectID, + OrganizationID: apiKey.DefaultProjectID, + Region: scw.RegionFrPar, + Zone: scw.ZoneFrPar1, + }) + if err != nil { + // Cleanup API Key if init failed + logger.Warningf("Init failed, cleaning API key.\n") + cleanErr := api.DeleteAPIKey(&iam.DeleteAPIKeyRequest{ + AccessKey: apiKey.AccessKey, + }) + if cleanErr != nil { + logger.Warningf("Failed to clean API key: %s\n", err.Error()) + } + return nil, err + } + + return resp, nil + }, + } +} + +type Token struct { + Jwt struct { + AudienceID string `json:"audienceId"` + CreatedAt time.Time `json:"createdAt"` + ExpiresAt time.Time `json:"expiresAt"` + IP string `json:"ip"` + IssuerID string `json:"issuerId"` + Jti string `json:"jti"` + UpdatedAt time.Time `json:"updatedAt"` + UserAgent string `json:"userAgent"` + } `json:"jwt"` + RenewToken string `json:"renewToken"` + Token string `json:"token"` +} diff --git a/internal/namespaces/login/webcallback/options.go b/internal/namespaces/login/webcallback/options.go new file mode 100644 index 0000000000..033d225e77 --- /dev/null +++ b/internal/namespaces/login/webcallback/options.go @@ -0,0 +1,9 @@ +package webcallback + +type Options func(*WebCallback) + +func WithPort(port int) Options { + return func(callback *WebCallback) { + callback.port = port + } +} diff --git a/internal/namespaces/login/webcallback/webcallback.go b/internal/namespaces/login/webcallback/webcallback.go new file mode 100644 index 0000000000..f5e770d909 --- /dev/null +++ b/internal/namespaces/login/webcallback/webcallback.go @@ -0,0 +1,103 @@ +package webcallback + +import ( + "context" + "errors" + "net" + "net/http" + "strconv" + "strings" + + "github.com/scaleway/scaleway-sdk-go/logger" +) + +// WebCallback is a web server that will wait for a callback +type WebCallback struct { + port int + + tokenChan chan string + errChan chan error + srv *http.Server + listener net.Listener +} + +func New(opts ...Options) *WebCallback { + wb := new(WebCallback) + for _, opt := range opts { + opt(wb) + } + + return wb +} + +func (wb *WebCallback) Start(ctx context.Context) error { + ctx, cancel := context.WithCancel(ctx) + wb.tokenChan = make(chan string) + wb.errChan = make(chan error) + + listener, err := net.Listen("tcp", ":"+strconv.Itoa(wb.port)) + if err != nil { + cancel() + return err + } + wb.listener = listener + wb.port = listener.Addr().(*net.TCPAddr).Port + wb.srv = &http.Server{Addr: ":" + strconv.Itoa(wb.port)} + + wb.srv.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !strings.HasSuffix(r.URL.Path, "callback") { + logger.Warningf("request has an unexpected path: %s", r.URL.Path) + } + wb.tokenChan <- r.URL.Query().Get("token") + + w.WriteHeader(200) + _, _ = w.Write([]byte(webpage)) + + cancel() + }) + + go func() { + err = wb.srv.Serve(listener) + if err != nil && !errors.Is(err, http.ErrServerClosed) { + wb.errChan <- err + } + cancel() + }() + + return nil +} + +func (wb *WebCallback) Wait(ctx context.Context) (string, error) { + defer wb.Close() + select { + case err := <-wb.errChan: + return "", err + case token := <-wb.tokenChan: + return token, nil + case <-ctx.Done(): + logger.Warningf("context canceled, closing web server") + return "", ctx.Err() + } +} + +func (wb *WebCallback) Close() { + err := wb.srv.Close() + if err != nil { + logger.Warningf("failed to close web server: %v", err) + } +} + +// Port returns the port used by the web server. It may be chosen randomly if let as default when starting server. +func (wb *WebCallback) Port() int { + return wb.port +} + +var webpage = ` + + + + +You can close this page. + + +` From 39f5f7c4967740c98bab56c4f70bac2c3a14f733 Mon Sep 17 00:00:00 2001 From: Jules Casteran Date: Fri, 16 Aug 2024 15:28:56 +0200 Subject: [PATCH 2/6] add debug and improve webpage content --- internal/namespaces/login/login.go | 16 ++++++++---- .../login/webcallback/webcallback.go | 25 +++++++++++++------ 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/internal/namespaces/login/login.go b/internal/namespaces/login/login.go index d30245e3e4..f9adcac6dc 100644 --- a/internal/namespaces/login/login.go +++ b/internal/namespaces/login/login.go @@ -22,7 +22,8 @@ func GetCommands() *core.Commands { } type loginArgs struct { - Port int `json:"port"` + Port int `json:"port"` + PrintURL bool `json:"print_url"` } func loginCommand() *core.Command { @@ -63,10 +64,15 @@ Once you connected to Scaleway, the profile should be configured. } callbackURL := fmt.Sprintf("http://localhost:%d/callback", wb.Port()) - err = open.Start("https://account.scaleway.com/authenticate?redirectToUrl=" + callbackURL) - if err != nil { - logger.Warningf("Failed to open web url, you may not have a default browser configured") - logger.Warningf(fmt.Sprintf("You can open it: %s", callbackURL)) + logger.Debugf("Web server started, waiting for callback on %s\n", callbackURL) + if args.PrintURL { + logger.Infof("https://account.scaleway.com/authenticate?redirectToUrl=" + callbackURL + "\n") + } else { + err = open.Start("https://account.scaleway.com/authenticate?redirectToUrl=" + callbackURL) + if err != nil { + logger.Warningf("Failed to open web url, you may not have a default browser configured") + logger.Warningf(fmt.Sprintf("You can open it: %s", callbackURL)) + } } fmt.Println("waiting for callback...") diff --git a/internal/namespaces/login/webcallback/webcallback.go b/internal/namespaces/login/webcallback/webcallback.go index f5e770d909..8d143fb07a 100644 --- a/internal/namespaces/login/webcallback/webcallback.go +++ b/internal/namespaces/login/webcallback/webcallback.go @@ -3,6 +3,7 @@ package webcallback import ( "context" "errors" + "fmt" "net" "net/http" "strconv" @@ -46,12 +47,18 @@ func (wb *WebCallback) Start(ctx context.Context) error { wb.srv.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !strings.HasSuffix(r.URL.Path, "callback") { - logger.Warningf("request has an unexpected path: %s", r.URL.Path) + w.WriteHeader(400) + _, _ = w.Write([]byte(webpageString("Invalid URL"))) + } + token := r.URL.Query().Get("token") + if token != "" { + wb.tokenChan <- r.URL.Query().Get("token") + w.WriteHeader(200) + _, _ = w.Write([]byte(webpageString("You can close this page."))) + } else { + w.WriteHeader(400) + _, _ = w.Write([]byte(webpageString("Invalid Token."))) } - wb.tokenChan <- r.URL.Query().Get("token") - - w.WriteHeader(200) - _, _ = w.Write([]byte(webpage)) cancel() }) @@ -92,12 +99,14 @@ func (wb *WebCallback) Port() int { return wb.port } -var webpage = ` +func webpageString(msg string) string { + return fmt.Sprintf(` -You can close this page. +%s -` +`, msg) +} From 73c125b401da100f2171225abc86e9dab5adaebe Mon Sep 17 00:00:00 2001 From: Jules Casteran Date: Mon, 26 Aug 2024 15:00:03 +0200 Subject: [PATCH 3/6] update doc and golden --- .../test-all-usage-login-usage.golden | 24 +++++++++++++++++++ cmd/scw/testdata/test-main-usage-usage.golden | 1 + docs/commands/login.md | 9 +++++++ 3 files changed, 34 insertions(+) create mode 100644 cmd/scw/testdata/test-all-usage-login-usage.golden create mode 100644 docs/commands/login.md diff --git a/cmd/scw/testdata/test-all-usage-login-usage.golden b/cmd/scw/testdata/test-all-usage-login-usage.golden new file mode 100644 index 0000000000..df426a3079 --- /dev/null +++ b/cmd/scw/testdata/test-all-usage-login-usage.golden @@ -0,0 +1,24 @@ +🎲🎲🎲 EXIT CODE: 0 🎲🎲🎲 +πŸŸ₯πŸŸ₯πŸŸ₯ STDERR️️ πŸŸ₯πŸŸ₯πŸŸ₯️ +Start an interactive connection to scaleway to initialize the active profile of the config +A webpage will open while the CLI will wait for a response. +Once you connected to Scaleway, the profile should be configured. + +USAGE: + scw login [arg=value ...] + +ARGS: + [port] The port number used to wait for browser's response + +FLAGS: + -h, --help help for login + +GLOBAL FLAGS: + -c, --config string The path to the config file + -D, --debug Enable debug mode + -o, --output string Output format: json or human, see 'scw help output' for more info (default "human") + -p, --profile string The config profile to use + +SEE ALSO: + # Init profile manually + scw init diff --git a/cmd/scw/testdata/test-main-usage-usage.golden b/cmd/scw/testdata/test-main-usage-usage.golden index c34cb983b9..ce2de18651 100644 --- a/cmd/scw/testdata/test-main-usage-usage.golden +++ b/cmd/scw/testdata/test-main-usage-usage.golden @@ -49,6 +49,7 @@ CONFIGURATION COMMANDS: config Config file management info Get info about current settings init Initialize the config + login Login to scaleway UTILITY COMMANDS: feedback Send feedback to the Scaleway CLI Team! diff --git a/docs/commands/login.md b/docs/commands/login.md new file mode 100644 index 0000000000..e966fc1694 --- /dev/null +++ b/docs/commands/login.md @@ -0,0 +1,9 @@ + +# Documentation for `scw login` +Start an interactive connection to scaleway to initialize the active profile of the config +A webpage will open while the CLI will wait for a response. +Once you connected to Scaleway, the profile should be configured. + + + + From 14fc58beecfe3c1f3c17e151ba67bdf5f403995a Mon Sep 17 00:00:00 2001 From: Jules Casteran Date: Mon, 26 Aug 2024 15:26:07 +0200 Subject: [PATCH 4/6] fix print-url and add tests --- internal/namespaces/login/login.go | 9 ++++-- .../login/webcallback/webcallback.go | 27 +++++++++++++++-- .../login/webcallback/webcallback_test.go | 30 +++++++++++++++++++ 3 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 internal/namespaces/login/webcallback/webcallback_test.go diff --git a/internal/namespaces/login/login.go b/internal/namespaces/login/login.go index f9adcac6dc..1052ed0527 100644 --- a/internal/namespaces/login/login.go +++ b/internal/namespaces/login/login.go @@ -22,7 +22,8 @@ func GetCommands() *core.Commands { } type loginArgs struct { - Port int `json:"port"` + Port int `json:"port"` + // PrintURL will print the account url instead of trying to open it with a browser PrintURL bool `json:"print_url"` } @@ -64,9 +65,11 @@ Once you connected to Scaleway, the profile should be configured. } callbackURL := fmt.Sprintf("http://localhost:%d/callback", wb.Port()) + logger.Debugf("Web server started, waiting for callback on %s\n", callbackURL) + if args.PrintURL { - logger.Infof("https://account.scaleway.com/authenticate?redirectToUrl=" + callbackURL + "\n") + fmt.Println("https://account.scaleway.com/authenticate?redirectToUrl=" + callbackURL) } else { err = open.Start("https://account.scaleway.com/authenticate?redirectToUrl=" + callbackURL) if err != nil { @@ -75,7 +78,7 @@ Once you connected to Scaleway, the profile should be configured. } } - fmt.Println("waiting for callback...") + fmt.Println("waiting for callback from browser...") token, err := wb.Wait(ctx) if err != nil { return nil, err diff --git a/internal/namespaces/login/webcallback/webcallback.go b/internal/namespaces/login/webcallback/webcallback.go index 8d143fb07a..b746bf7495 100644 --- a/internal/namespaces/login/webcallback/webcallback.go +++ b/internal/namespaces/login/webcallback/webcallback.go @@ -8,6 +8,7 @@ import ( "net/http" "strconv" "strings" + "time" "github.com/scaleway/scaleway-sdk-go/logger" ) @@ -33,8 +34,8 @@ func New(opts ...Options) *WebCallback { func (wb *WebCallback) Start(ctx context.Context) error { ctx, cancel := context.WithCancel(ctx) - wb.tokenChan = make(chan string) - wb.errChan = make(chan error) + wb.tokenChan = make(chan string, 1) + wb.errChan = make(chan error, 1) listener, err := net.Listen("tcp", ":"+strconv.Itoa(wb.port)) if err != nil { @@ -74,6 +75,28 @@ func (wb *WebCallback) Start(ctx context.Context) error { return nil } +// Trigger will trigger currently waiting callback. Made for tests +func (wb *WebCallback) Trigger(token string, timeout time.Duration) error { + req, err := http.NewRequest(http.MethodGet, "http://localhost:"+strconv.Itoa(wb.port)+"/callback", nil) + if err != nil { + return err + } + + q := req.URL.Query() + q.Add("token", token) + req.URL.RawQuery = q.Encode() + + client := http.Client{Timeout: timeout} + + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + return nil +} + func (wb *WebCallback) Wait(ctx context.Context) (string, error) { defer wb.Close() select { diff --git a/internal/namespaces/login/webcallback/webcallback_test.go b/internal/namespaces/login/webcallback/webcallback_test.go new file mode 100644 index 0000000000..9d42420378 --- /dev/null +++ b/internal/namespaces/login/webcallback/webcallback_test.go @@ -0,0 +1,30 @@ +package webcallback_test + +import ( + "context" + "testing" + "time" + + "github.com/scaleway/scaleway-cli/v2/internal/namespaces/login/webcallback" + "github.com/stretchr/testify/assert" +) + +func TestWebCallback(t *testing.T) { + wb := webcallback.New() + + t.Cleanup(func() { + wb.Close() + }) + assert.NoError(t, wb.Start(context.Background())) + assert.NoError(t, wb.Trigger("test-token", time.Second)) + + ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second) + t.Cleanup(cancelFunc) + + resp, err := wb.Wait(ctx) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "test-token", resp) +} From 484be5805f5ac9aa4fed979f6eb6c072c6ac9d91 Mon Sep 17 00:00:00 2001 From: Jules Casteran Date: Mon, 26 Aug 2024 15:40:58 +0200 Subject: [PATCH 5/6] lint --- internal/namespaces/login/login.go | 10 ++++++---- .../login/webcallback/webcallback.go | 19 +++++++++---------- .../login/webcallback/webcallback_test.go | 2 +- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/internal/namespaces/login/login.go b/internal/namespaces/login/login.go index 1052ed0527..df765b3d5f 100644 --- a/internal/namespaces/login/login.go +++ b/internal/namespaces/login/login.go @@ -59,22 +59,24 @@ Once you connected to Scaleway, the profile should be configured. } wb := webcallback.New(opts...) - err := wb.Start(ctx) + err := wb.Start() if err != nil { return nil, err } callbackURL := fmt.Sprintf("http://localhost:%d/callback", wb.Port()) + accountURL := "https://account.scaleway.com/authenticate?redirectToUrl=" + callbackURL + logger.Debugf("Web server started, waiting for callback on %s\n", callbackURL) if args.PrintURL { - fmt.Println("https://account.scaleway.com/authenticate?redirectToUrl=" + callbackURL) + fmt.Println(accountURL) } else { - err = open.Start("https://account.scaleway.com/authenticate?redirectToUrl=" + callbackURL) + err = open.Start(accountURL) if err != nil { logger.Warningf("Failed to open web url, you may not have a default browser configured") - logger.Warningf(fmt.Sprintf("You can open it: %s", callbackURL)) + logger.Warningf("You can open it: " + accountURL) } } diff --git a/internal/namespaces/login/webcallback/webcallback.go b/internal/namespaces/login/webcallback/webcallback.go index b746bf7495..8c21d37cf4 100644 --- a/internal/namespaces/login/webcallback/webcallback.go +++ b/internal/namespaces/login/webcallback/webcallback.go @@ -32,36 +32,36 @@ func New(opts ...Options) *WebCallback { return wb } -func (wb *WebCallback) Start(ctx context.Context) error { - ctx, cancel := context.WithCancel(ctx) +func (wb *WebCallback) Start() error { wb.tokenChan = make(chan string, 1) wb.errChan = make(chan error, 1) listener, err := net.Listen("tcp", ":"+strconv.Itoa(wb.port)) if err != nil { - cancel() return err } wb.listener = listener wb.port = listener.Addr().(*net.TCPAddr).Port - wb.srv = &http.Server{Addr: ":" + strconv.Itoa(wb.port)} + wb.srv = &http.Server{ + Addr: ":" + strconv.Itoa(wb.port), + ReadHeaderTimeout: time.Second * 5, + ReadTimeout: time.Second * 5, + } wb.srv.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !strings.HasSuffix(r.URL.Path, "callback") { - w.WriteHeader(400) + w.WriteHeader(http.StatusBadRequest) _, _ = w.Write([]byte(webpageString("Invalid URL"))) } token := r.URL.Query().Get("token") if token != "" { wb.tokenChan <- r.URL.Query().Get("token") - w.WriteHeader(200) + w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(webpageString("You can close this page."))) } else { - w.WriteHeader(400) + w.WriteHeader(http.StatusBadRequest) _, _ = w.Write([]byte(webpageString("Invalid Token."))) } - - cancel() }) go func() { @@ -69,7 +69,6 @@ func (wb *WebCallback) Start(ctx context.Context) error { if err != nil && !errors.Is(err, http.ErrServerClosed) { wb.errChan <- err } - cancel() }() return nil diff --git a/internal/namespaces/login/webcallback/webcallback_test.go b/internal/namespaces/login/webcallback/webcallback_test.go index 9d42420378..42d1223a7b 100644 --- a/internal/namespaces/login/webcallback/webcallback_test.go +++ b/internal/namespaces/login/webcallback/webcallback_test.go @@ -15,7 +15,7 @@ func TestWebCallback(t *testing.T) { t.Cleanup(func() { wb.Close() }) - assert.NoError(t, wb.Start(context.Background())) + assert.NoError(t, wb.Start()) assert.NoError(t, wb.Trigger("test-token", time.Second)) ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second) From 83727cd41def6a0492c6f741e506e1f5f9b6cd88 Mon Sep 17 00:00:00 2001 From: Jules Casteran Date: Mon, 26 Aug 2024 15:46:58 +0200 Subject: [PATCH 6/6] rename init.InitCommand to init.Command --- internal/namespaces/init/init.go | 10 +++++----- internal/namespaces/login/login.go | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/namespaces/init/init.go b/internal/namespaces/init/init.go index 745337790a..aa909e6c0e 100644 --- a/internal/namespaces/init/init.go +++ b/internal/namespaces/init/init.go @@ -54,10 +54,10 @@ See below the schema `scw init` follows to ask for default config: */ func GetCommands() *core.Commands { - return core.NewCommands(InitCommand()) + return core.NewCommands(Command()) } -type InitArgs struct { +type Args struct { AccessKey string SecretKey string ProjectID string @@ -70,7 +70,7 @@ type InitArgs struct { InstallAutocomplete *bool } -func InitCommand() *core.Command { +func Command() *core.Command { return &core.Command{ Groups: []string{"config"}, Short: `Initialize the config`, @@ -83,7 +83,7 @@ Default path for configuration file is based on the following priority order: - $USERPROFILE/.config/scw/config.yaml`, Namespace: "init", AllowAnonymousClient: true, - ArgsType: reflect.TypeOf(InitArgs{}), + ArgsType: reflect.TypeOf(Args{}), ArgSpecs: core.ArgSpecs{ { Name: "secret-key", @@ -132,7 +132,7 @@ Default path for configuration file is based on the following priority order: }, }, Run: func(ctx context.Context, argsI interface{}) (i interface{}, e error) { - args := argsI.(*InitArgs) + args := argsI.(*Args) profileName := core.ExtractProfileName(ctx) configPath := core.ExtractConfigPath(ctx) diff --git a/internal/namespaces/login/login.go b/internal/namespaces/login/login.go index df765b3d5f..839e918a2b 100644 --- a/internal/namespaces/login/login.go +++ b/internal/namespaces/login/login.go @@ -112,7 +112,7 @@ Once you connected to Scaleway, the profile should be configured. return nil, err } - resp, err := initCommand.InitCommand().Run(ctx, &initCommand.InitArgs{ + resp, err := initCommand.Command().Run(ctx, &initCommand.Args{ AccessKey: apiKey.AccessKey, SecretKey: *apiKey.SecretKey, ProjectID: apiKey.DefaultProjectID,