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

Manually merged
dananglin merged 1 commit from refactor-login into main 2024-02-25 09:24:41 +00:00
7 changed files with 109 additions and 110 deletions

View file

@ -1,18 +1,14 @@
package main package main
import ( import (
"context"
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
"os/exec"
"runtime"
"strings" "strings"
"codeflow.dananglin.me.uk/apollo/enbas/internal"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client" "codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config" "codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"golang.org/x/oauth2" "codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
) )
type loginCommand struct { type loginCommand struct {
@ -20,21 +16,7 @@ type loginCommand struct {
instance string instance string
} }
var ( var errInstanceNotSet = errors.New("the instance flag is not set")
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.
`
func newLoginCommand(name, summary string) *loginCommand { func newLoginCommand(name, summary string) *loginCommand {
command := loginCommand{ command := loginCommand{
@ -76,20 +58,20 @@ func (c *loginCommand) Execute() error {
return fmt.Errorf("unable to register the application; %w", err) return fmt.Errorf("unable to register the application; %w", err)
} }
oauth2Conf := oauth2.Config{ consentPageURL := gtsClient.AuthCodeURL()
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 := 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) fmt.Printf(consentMessageFormat, consentPageURL)
@ -100,9 +82,8 @@ func (c *loginCommand) Execute() error {
return fmt.Errorf("failed to read access code; %w", err) return fmt.Errorf("failed to read access code; %w", err)
} }
gtsClient.Authentication, err = addAccessToken(gtsClient.Authentication, oauth2Conf, code) if err := gtsClient.UpdateToken(code); err != nil {
if err != nil { return fmt.Errorf("unable to update the client's access token; %w", err)
return fmt.Errorf("unable to get the access token; %w", err)
} }
account, err := gtsClient.VerifyCredentials() account, err := gtsClient.VerifyCredentials()
@ -119,41 +100,3 @@ func (c *loginCommand) Execute() error {
return nil 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 go 1.22.0
require ( require golang.org/x/net v0.21.0
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
)

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 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 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" "fmt"
"io" "io"
"net/http" "net/http"
"net/url"
"time" "time"
"codeflow.dananglin.me.uk/apollo/enbas/internal" "codeflow.dananglin.me.uk/apollo/enbas/internal"
@ -44,6 +45,18 @@ func NewClient(authentication config.Authentication) *Client {
return &gtsClient 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) { func (g *Client) VerifyCredentials() (model.Account, error) {
path := "/api/v1/accounts/verify_credentials" path := "/api/v1/accounts/verify_credentials"
url := g.Authentication.Instance + path url := g.Authentication.Instance + path

View file

@ -10,7 +10,7 @@ import (
"codeflow.dananglin.me.uk/apollo/enbas/internal/model" "codeflow.dananglin.me.uk/apollo/enbas/internal/model"
) )
type RegisterRequest struct { type registerRequest struct {
ClientName string `json:"client_name"` ClientName string `json:"client_name"`
RedirectUris string `json:"redirect_uris"` RedirectUris string `json:"redirect_uris"`
Scopes string `json:"scopes"` Scopes string `json:"scopes"`
@ -18,7 +18,7 @@ type RegisterRequest struct {
} }
func (g *Client) Register() error { func (g *Client) Register() error {
params := RegisterRequest{ params := registerRequest{
ClientName: internal.ApplicationName, ClientName: internal.ApplicationName,
RedirectUris: internal.RedirectUri, RedirectUris: internal.RedirectUri,
Scopes: "read write", Scopes: "read write",
@ -31,9 +31,7 @@ func (g *Client) Register() error {
} }
requestBody := bytes.NewBuffer(data) requestBody := bytes.NewBuffer(data)
url := g.Authentication.Instance + "/api/v1/apps"
path := "/api/v1/apps"
url := g.Authentication.Instance + path
var app model.Application 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 package utilities
import ( import (
"os/exec"
"regexp" "regexp"
"runtime"
"strings" "strings"
"time" "time"
"unicode" "unicode"
@ -16,6 +18,20 @@ const (
green = "\033[32m" 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 { func StripHTMLTags(text string) string {
token := html.NewTokenizer(strings.NewReader(text)) token := html.NewTokenizer(strings.NewReader(text))