refactor: remove dependency on golang.org/x/oauth2

This commit refactors the login flow to a GTS server thanks to an update
in the official GTS documentation.

golang.org/x/oauth2 is no longer needed.

Documentation reference:
- https://docs.gotosocial.org/en/latest/api/authentication/
This commit is contained in:
Dan Anglin 2024-02-25 06:41:13 +00:00
parent c39b173cd6
commit 5a795c8ae0
Signed by: dananglin
GPG key ID: 0C1D44CFBEE68638
7 changed files with 109 additions and 110 deletions

View file

@ -1,18 +1,14 @@
package main
import (
"context"
"errors"
"flag"
"fmt"
"os/exec"
"runtime"
"strings"
"codeflow.dananglin.me.uk/apollo/enbas/internal"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"golang.org/x/oauth2"
"codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
)
type loginCommand struct {
@ -20,21 +16,7 @@ type loginCommand struct {
instance string
}
var (
errEmptyAccessToken = errors.New("received an empty access token")
errInstanceNotSet = errors.New("the instance flag is not set")
)
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.
`
var errInstanceNotSet = errors.New("the instance flag is not set")
func newLoginCommand(name, summary string) *loginCommand {
command := loginCommand{
@ -76,20 +58,20 @@ func (c *loginCommand) Execute() error {
return fmt.Errorf("unable to register the application; %w", err)
}
oauth2Conf := oauth2.Config{
ClientID: gtsClient.Authentication.ClientID,
ClientSecret: gtsClient.Authentication.ClientSecret,
Scopes: []string{"read"},
RedirectURL: internal.RedirectUri,
Endpoint: oauth2.Endpoint{
AuthURL: gtsClient.Authentication.Instance + "/oauth/authorize",
TokenURL: gtsClient.Authentication.Instance + "/oauth/token",
},
}
consentPageURL := gtsClient.AuthCodeURL()
consentPageURL := authCodeURL(oauth2Conf)
utilities.OpenLink(consentPageURL)
openLink(consentPageURL)
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.
`
fmt.Printf(consentMessageFormat, consentPageURL)
@ -100,9 +82,8 @@ func (c *loginCommand) Execute() error {
return fmt.Errorf("failed to read access code; %w", err)
}
gtsClient.Authentication, err = addAccessToken(gtsClient.Authentication, oauth2Conf, code)
if err != nil {
return fmt.Errorf("unable to get the access token; %w", err)
if err := gtsClient.UpdateToken(code); err != nil {
return fmt.Errorf("unable to update the client's access token; %w", err)
}
account, err := gtsClient.VerifyCredentials()
@ -119,41 +100,3 @@ func (c *loginCommand) Execute() error {
return nil
}
func authCodeURL(oauth2Conf oauth2.Config) string {
url := oauth2Conf.AuthCodeURL(
"state",
oauth2.AccessTypeOffline,
) + "&client_name=" + internal.ApplicationName
return url
}
func addAccessToken(authentication config.Authentication, oauth2Conf oauth2.Config, code string) (config.Authentication, error) {
token, err := oauth2Conf.Exchange(context.Background(), code)
if err != nil {
return config.Authentication{}, fmt.Errorf("unable to exchange the code for an access token; %w", err)
}
if token == nil || token.AccessToken == "" {
return config.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()
}

11
go.mod
View file

@ -2,13 +2,4 @@ module codeflow.dananglin.me.uk/apollo/enbas
go 1.22.0
require (
golang.org/x/net v0.21.0
golang.org/x/oauth2 v0.17.0
)
require (
github.com/golang/protobuf v1.5.3 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.31.0 // indirect
)
require golang.org/x/net v0.21.0

22
go.sum
View file

@ -1,24 +1,2 @@
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=

View file

@ -6,6 +6,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"time"
"codeflow.dananglin.me.uk/apollo/enbas/internal"
@ -44,6 +45,18 @@ func NewClient(authentication config.Authentication) *Client {
return &gtsClient
}
func (g *Client) AuthCodeURL() string {
format := "%s/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code"
escapedRedirectURI := url.QueryEscape(internal.RedirectUri)
return fmt.Sprintf(
format,
g.Authentication.Instance,
g.Authentication.ClientID,
escapedRedirectURI,
)
}
func (g *Client) VerifyCredentials() (model.Account, error) {
path := "/api/v1/accounts/verify_credentials"
url := g.Authentication.Instance + path

View file

@ -10,7 +10,7 @@ import (
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
)
type RegisterRequest struct {
type registerRequest struct {
ClientName string `json:"client_name"`
RedirectUris string `json:"redirect_uris"`
Scopes string `json:"scopes"`
@ -18,7 +18,7 @@ type RegisterRequest struct {
}
func (g *Client) Register() error {
params := RegisterRequest{
params := registerRequest{
ClientName: internal.ApplicationName,
RedirectUris: internal.RedirectUri,
Scopes: "read write",
@ -31,9 +31,7 @@ func (g *Client) Register() error {
}
requestBody := bytes.NewBuffer(data)
path := "/api/v1/apps"
url := g.Authentication.Instance + path
url := g.Authentication.Instance + "/api/v1/apps"
var app model.Application

60
internal/client/token.go Normal file
View file

@ -0,0 +1,60 @@
package client
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"codeflow.dananglin.me.uk/apollo/enbas/internal"
)
var errEmptyAccessToken = errors.New("received an empty access token")
type tokenRequest struct {
RedirectUri string `json:"redirect_uri"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
GrantType string `json:"grant_type"`
Code string `json:"code"`
}
type tokenResponse struct {
AccessToken string `json:"access_token"`
CreatedAt int `json:"created_at"`
Scope string `json:"scope"`
TokenType string `json:"token_type"`
}
func (g *Client) UpdateToken(code string) error {
params := tokenRequest{
RedirectUri: internal.RedirectUri,
ClientID: g.Authentication.ClientID,
ClientSecret: g.Authentication.ClientSecret,
GrantType: "authorization_code",
Code: code,
}
data, err := json.Marshal(params)
if err != nil {
return fmt.Errorf("unable to marshal the request body; %w", err)
}
requestBody := bytes.NewBuffer(data)
url := g.Authentication.Instance + "/oauth/token"
var response tokenResponse
if err := g.sendRequest(http.MethodPost, url, requestBody, &response); err != nil {
return fmt.Errorf("received an error after sending the token request; %w", err)
}
if response.AccessToken == "" {
return errEmptyAccessToken
}
g.Authentication.AccessToken = response.AccessToken
return nil
}

View file

@ -1,7 +1,9 @@
package utilities
import (
"os/exec"
"regexp"
"runtime"
"strings"
"time"
"unicode"
@ -16,6 +18,20 @@ const (
green = "\033[32m"
)
func OpenLink(url string) {
var open string
if runtime.GOOS == "linux" {
open = "xdg-open"
} else {
return
}
command := exec.Command(open, url)
_ = command.Start()
}
func StripHTMLTags(text string) string {
token := html.NewTokenizer(strings.NewReader(text))