package main import ( "context" "errors" "fmt" "os/exec" "runtime" "strings" "golang.org/x/oauth2" ) var errEmptyAccessToken = errors.New("received an empty access token") var consentMessageFormat = ` You'll need to sign into your GoToSocial's consent page in order to generate the out-of-band token to continue with the application's login process. Your browser may have opened the link to the consent page already. If not, please copy and paste the link below to your browser: %s Once you have the code please copy and paste it below. ` func loginWithOauth2() error { var err error var instance string fmt.Print("Please enter the instance URL: ") if _, err := fmt.Scanln(&instance); err != nil { return fmt.Errorf("unable to read user input for the instance value; %w", err) } if !strings.HasPrefix(instance, "https") || !strings.HasPrefix(instance, "http") { instance = "https://" + instance } for strings.HasSuffix(instance, "/") { instance = instance[:len(instance)-1] } authentication := Authentication{ Instance: instance, } client := newGtsClient(authentication) if err := client.register(); err != nil { return fmt.Errorf("unable to register the application; %w", err) } consentPageURL := authCodeURL(client.authentication) openLink(consentPageURL) fmt.Printf(consentMessageFormat, consentPageURL) var code string fmt.Print("Out-of-band token: ") if _, err := fmt.Scanln(&code); err != nil { return fmt.Errorf("failed to read access code; %w", err) } client.authentication, err = addAccessToken(client.authentication, code) if err != nil { return fmt.Errorf("unable to get the access token; %w", err) } account, err := client.verifyCredentials() if err != nil { return fmt.Errorf("unable to verify the credentials; %w", err) } loginName, err := saveAuthenticationConfig(account.Username, client.authentication) if err != nil { return fmt.Errorf("unable to save the authentication details; %w", err) } fmt.Printf("Successfully logged into %s\n", loginName) return nil } func authCodeURL(account Authentication) string { config := oauth2.Config{ ClientID: account.ClientID, ClientSecret: account.ClientSecret, Scopes: []string{"read"}, RedirectURL: redirectUri, Endpoint: oauth2.Endpoint{ AuthURL: account.Instance + "/oauth/authorize", TokenURL: account.Instance + "/oauth/token", }, } url := config.AuthCodeURL("state", oauth2.AccessTypeOffline) + fmt.Sprintf("&client_name=%s", applicationName) return url } func addAccessToken(authentication Authentication, code string) (Authentication, error) { config := oauth2.Config{ ClientID: authentication.ClientID, ClientSecret: authentication.ClientSecret, Scopes: []string{"read", "write"}, RedirectURL: redirectUri, Endpoint: oauth2.Endpoint{ AuthURL: authentication.Instance + "/oauth/authorize", TokenURL: authentication.Instance + "/oauth/token", }, } token, err := config.Exchange(context.Background(), code) if err != nil { return Authentication{}, fmt.Errorf("unable to exchange the code for an access token; %w", err) } if token == nil || token.AccessToken == "" { return Authentication{}, errEmptyAccessToken } authentication.AccessToken = token.AccessToken return authentication, nil } func openLink(url string) { var open string if runtime.GOOS == "linux" { open = "xdg-open" } else { return } command := exec.Command(open, url) _ = command.Start() }