diff --git a/cmd/service_add.go b/cmd/service_add.go
index 634a511..0bf671d 100644
--- a/cmd/service_add.go
+++ b/cmd/service_add.go
@@ -27,9 +27,11 @@ import (
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
+ "go.uploadedlobster.com/scotty/internal/backends"
"go.uploadedlobster.com/scotty/internal/cli"
"go.uploadedlobster.com/scotty/internal/config"
"go.uploadedlobster.com/scotty/internal/i18n"
+ "go.uploadedlobster.com/scotty/internal/models"
)
var serviceAddCmd = &cobra.Command{
@@ -71,6 +73,10 @@ var serviceAddCmd = &cobra.Command{
err = service.Save()
cobra.CheckErr(err)
fmt.Println(i18n.Tr("Saved service %v using backend %v", service.Name, service.Backend))
+
+ // Check whether authentication is required
+ err = promptForAuth(service)
+ cobra.CheckErr(err)
},
}
@@ -87,3 +93,25 @@ func init() {
// is called directly, e.g.:
// serviceAddCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
+
+func promptForAuth(service config.ServiceConfig) error {
+ backend, err := backends.ResolveBackend[models.OAuth2Authenticator](service)
+ if err != nil {
+ // No authentication required, return
+ return nil
+ }
+
+ doAuth, err := cli.PromptYesNo(
+ i18n.Tr("The backend %v requires authentication. Authenticate now?", service.Backend),
+ true,
+ )
+ if err != nil {
+ return err
+ }
+ if !doAuth {
+ return nil
+ }
+
+ cli.AuthenticationFlow(service, backend)
+ return nil
+}
diff --git a/cmd/service_auth.go b/cmd/service_auth.go
index 91125ad..ddab35d 100644
--- a/cmd/service_auth.go
+++ b/cmd/service_auth.go
@@ -17,20 +17,10 @@ Scotty. If not, see .
package cmd
import (
- "fmt"
- "os"
-
- "github.com/cli/browser"
"github.com/spf13/cobra"
- "github.com/spf13/viper"
- "go.uploadedlobster.com/scotty/internal/auth"
"go.uploadedlobster.com/scotty/internal/backends"
"go.uploadedlobster.com/scotty/internal/cli"
- "go.uploadedlobster.com/scotty/internal/config"
- "go.uploadedlobster.com/scotty/internal/i18n"
"go.uploadedlobster.com/scotty/internal/models"
- "go.uploadedlobster.com/scotty/internal/storage"
- "golang.org/x/oauth2"
)
var serviceAuthCmd = &cobra.Command{
@@ -45,49 +35,7 @@ multiple services using the same backend but different authentication.`,
cobra.CheckErr(err)
backend, err := backends.ResolveBackend[models.OAuth2Authenticator](serviceConfig)
cobra.CheckErr(err)
-
- redirectURL, err := backends.BuildRedirectURL(viper.GetViper(), backend.Name())
- cobra.CheckErr(err)
-
- // The backend must provide an authentication strategy
- strategy := backend.OAuth2Strategy(redirectURL)
-
- // use PKCE to protect against CSRF attacks
- // https://www.ietf.org/archive/id/draft-ietf-oauth-security-topics-22.html#name-countermeasures-6
- verifier := oauth2.GenerateVerifier()
-
- state := auth.RandomState()
- // Redirect user to consent page to ask for permission specified scopes.
- authUrl := strategy.AuthCodeURL(verifier, state)
-
- // Start an HTTP server to listen for the response
- responseChan := make(chan auth.CodeResponse)
- auth.RunOauth2CallbackServer(*redirectURL, authUrl.Param, responseChan)
-
- // Open the URL
- fmt.Println(i18n.Tr("Visit the URL for authorization: %v", authUrl.Url))
- err = browser.OpenURL(authUrl.Url)
- cobra.CheckErr(err)
-
- // Retrieve the code from the authentication callback
- code := <-responseChan
- if code.State != authUrl.State {
- cobra.CompErrorln(i18n.Tr("Error: OAuth state mismatch"))
- os.Exit(1)
- }
-
- // Exchange the code for the authentication token
- tok, err := strategy.ExchangeToken(code, verifier)
- cobra.CheckErr(err)
-
- // Store the retrieved token in the database
- db, err := storage.New(config.DatabasePath())
- cobra.CheckErr(err)
-
- err = db.SetOAuth2Token(serviceConfig.Name, tok)
- cobra.CheckErr(err)
-
- fmt.Println(i18n.Tr("Access token received, you can use %v now.\n", serviceConfig.Name))
+ cli.AuthenticationFlow(serviceConfig, backend)
},
}
diff --git a/internal/cli/auth.go b/internal/cli/auth.go
new file mode 100644
index 0000000..fc5c889
--- /dev/null
+++ b/internal/cli/auth.go
@@ -0,0 +1,77 @@
+/*
+Copyright © 2023 Philipp Wolfer
+
+Scotty is free software: you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later version.
+
+Scotty is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+Scotty. If not, see .
+*/
+
+package cli
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/cli/browser"
+ "github.com/spf13/cobra"
+ "github.com/spf13/viper"
+ "go.uploadedlobster.com/scotty/internal/auth"
+ "go.uploadedlobster.com/scotty/internal/backends"
+ "go.uploadedlobster.com/scotty/internal/config"
+ "go.uploadedlobster.com/scotty/internal/i18n"
+ "go.uploadedlobster.com/scotty/internal/models"
+ "go.uploadedlobster.com/scotty/internal/storage"
+ "golang.org/x/oauth2"
+)
+
+func AuthenticationFlow(service config.ServiceConfig, backend models.OAuth2Authenticator) {
+ redirectURL, err := backends.BuildRedirectURL(viper.GetViper(), backend.Name())
+ cobra.CheckErr(err)
+
+ // The backend must provide an authentication strategy
+ strategy := backend.OAuth2Strategy(redirectURL)
+
+ // use PKCE to protect against CSRF attacks
+ // https://www.ietf.org/archive/id/draft-ietf-oauth-security-topics-22.html#name-countermeasures-6
+ verifier := oauth2.GenerateVerifier()
+
+ state := auth.RandomState()
+ // Redirect user to consent page to ask for permission specified scopes.
+ authUrl := strategy.AuthCodeURL(verifier, state)
+
+ // Start an HTTP server to listen for the response
+ responseChan := make(chan auth.CodeResponse)
+ auth.RunOauth2CallbackServer(*redirectURL, authUrl.Param, responseChan)
+
+ // Open the URL
+ fmt.Println(i18n.Tr("Visit the URL for authorization: %v", authUrl.Url))
+ err = browser.OpenURL(authUrl.Url)
+ cobra.CheckErr(err)
+
+ // Retrieve the code from the authentication callback
+ code := <-responseChan
+ if code.State != authUrl.State {
+ cobra.CompErrorln(i18n.Tr("Error: OAuth state mismatch"))
+ os.Exit(1)
+ }
+
+ // Exchange the code for the authentication token
+ tok, err := strategy.ExchangeToken(code, verifier)
+ cobra.CheckErr(err)
+
+ // Store the retrieved token in the database
+ db, err := storage.New(config.DatabasePath())
+ cobra.CheckErr(err)
+
+ err = db.SetOAuth2Token(service.Name, tok)
+ cobra.CheckErr(err)
+
+ fmt.Println(i18n.Tr("Access token received, you can use %v now.\n", service.Name))
+}