checkpoint: auths are now saved to file

This commit is contained in:
Dan Anglin 2024-02-20 17:54:23 +00:00
parent 624cd561ed
commit d3c67562ec
Signed by: dananglin
GPG key ID: 0C1D44CFBEE68638
5 changed files with 209 additions and 18 deletions

58
account.go Normal file
View file

@ -0,0 +1,58 @@
package main
type Account struct {
Acct string `json:"acct"`
Avatar string `json:"avatar"`
AvatarStatic string `json:"avatar_static"`
Bot bool `json:"bot"`
CreatedAt string `json:"created_at"`
CustomCSS string `json:"custom_css"`
Discoverable bool `json:"discoverable"`
DisplayName string `json:"display_name"`
Emojis []Emoji `json:"emojis"`
EnableRSS bool `json:"enable_rss"`
Fields []Field `json:"fields"`
FollowersCount int `json:"followers_count"`
FollowingCount int `json:"following_count"`
Header string `json:"header"`
HeaderStatic string `json:"header_static"`
ID string `json:"id"`
LastStatusAt string `json:"last_status_at"`
Locked bool `json:"locked"`
MuteExpiresAt string `json:"mute_expires_at"`
Note string `json:"note"`
Role AccountRole `json:"role"`
Source Source `json:"source"`
StatusCount int `json:"statuses_count"`
Suspended bool `json:"suspended"`
URL string `json:"url"`
Username string `json:"username"`
}
type Emoji struct {
Category string `json:"category"`
Shortcode string `json:"shortcode"`
StaticURL string `json:"static_url"`
URL string `json:"url"`
VisibleInPicker bool `json:"visible_in_picker"`
}
type AccountRole struct {
Name string `json:"name"`
}
type Source struct {
Fields []Field `json:"fields"`
FollowRequestCount int `json:"follow_requests_count"`
Language string `json:"language"`
Note string `json:"note"`
Privacy string `json:"string"`
Sensitive bool `json:"sensitive"`
StatusContentType string `json:"status_content_type"`
}
type Field struct {
Name string `json:"name"`
Value string `json:"value"`
VerifiedAt string `json:"verified_at"`
}

View file

@ -1,15 +1,18 @@
package main package main
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"time"
) )
type gtsClient struct { type gtsClient struct {
authentication Authentication authentication Authentication
httpClient http.Client httpClient http.Client
userAgent string userAgent string
timeout time.Duration
} }
func newGtsClient(authentication Authentication) *gtsClient { func newGtsClient(authentication Authentication) *gtsClient {
@ -19,11 +22,33 @@ func newGtsClient(authentication Authentication) *gtsClient {
authentication: authentication, authentication: authentication,
httpClient: httpClient, httpClient: httpClient,
userAgent: userAgent, userAgent: userAgent,
timeout: 5 * time.Second,
} }
return &client return &client
} }
func (g *gtsClient) verifyCredentials() (Account, error) {
path := "/api/v1/accounts/verify_credentials"
url := g.authentication.Instance + path
ctx, cancel := context.WithTimeout(context.Background(), g.timeout)
defer cancel()
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return Account{}, fmt.Errorf("unable to create the HTTP request; %w", err)
}
var account Account
if err := g.sendRequest(request, &account); err != nil {
return Account{}, fmt.Errorf("received an error after sending the request to verify the credentials; %w", err)
}
return account, nil
}
func (g *gtsClient) sendRequest(request *http.Request, object any) error { func (g *gtsClient) sendRequest(request *http.Request, object any) error {
request.Header.Set("Content-Type", "application/json; charset=utf-8") request.Header.Set("Content-Type", "application/json; charset=utf-8")
request.Header.Set("Accept", "application/json; charset=utf-8") request.Header.Set("Accept", "application/json; charset=utf-8")

117
config.go Normal file
View file

@ -0,0 +1,117 @@
package main
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
)
type AuthenticationConfig struct {
CurrentAccount string `json:"currentAccount"`
Authentications map[string]Authentication `json:"authentications"`
}
type Authentication struct {
Instance string `json:"instance"`
ClientID string `json:"clientId"`
ClientSecret string `json:"clientSecret"`
AccessToken string `json:"accessToken"`
}
func saveAuthenticationConfig(username string, authentication Authentication) error {
if err := ensureConfigDir(); err != nil {
return fmt.Errorf("unable to ensure the configuration directory; %w", err)
}
var config AuthenticationConfig
filepath := authenticationConfigFile()
if _, err := os.Stat(filepath); err != nil {
if !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("unknown error received when running stat on %s; %w", filepath, err)
}
config.Authentications = make(map[string]Authentication)
} else {
config, err = newAuthenticationConfigFromFile()
if err != nil {
return fmt.Errorf("unable to retrieve the existing authentication configuration; %w", err)
}
}
instance := ""
if strings.HasPrefix(authentication.Instance, "https://") {
instance = strings.TrimPrefix(authentication.Instance, "https://")
} else if strings.HasPrefix(authentication.Instance, "http://") {
instance = strings.TrimPrefix(authentication.Instance, "http://")
}
authenticationName := username + "@" + instance
config.CurrentAccount = authenticationName
config.Authentications[authenticationName] = authentication
file, err := os.Create(authenticationConfigFile())
if err != nil {
return fmt.Errorf("unable to open the config file; %w", err)
}
if err := json.NewEncoder(file).Encode(config); err != nil {
return fmt.Errorf("unable to save the JSON data to the authentication config file; %w", err)
}
return nil
}
func newAuthenticationConfigFromFile() (AuthenticationConfig, error) {
path := authenticationConfigFile()
file, err := os.Open(path)
if err != nil {
return AuthenticationConfig{}, fmt.Errorf("unable to open %s, %w", path, err)
}
defer file.Close()
var config AuthenticationConfig
if err := json.NewDecoder(file).Decode(&config); err != nil {
return AuthenticationConfig{}, fmt.Errorf("unable to decode the JSON data; %w", err)
}
return config, nil
}
func authenticationConfigFile() string {
return filepath.Join(configDir(), "authentications.json")
}
func configDir() string {
rootDir, err := os.UserConfigDir()
if err != nil {
rootDir = "."
}
return filepath.Join(rootDir, applicationName)
}
func ensureConfigDir() error {
dir := configDir()
if _, err := os.Stat(dir); err != nil {
if errors.Is(err, os.ErrNotExist) {
if err := os.MkdirAll(dir, 0o750); err != nil {
return fmt.Errorf("unable to create %s; %w", dir, err)
}
} else {
return fmt.Errorf("unknown error received when running stat on %s; %w", dir, err)
}
}
return nil
}

View file

@ -11,18 +11,6 @@ import (
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
type AuthenticationConfig struct {
CurrentAccount string `json:"currentAccount"`
Authentications map[string]Authentication `json:"authentications"`
}
type Authentication struct {
Instance string `json:"instance"`
ClientID string `json:"clientId"`
ClientSecret string `json:"clientSecret"`
AccessToken string `json:"accessToken"`
}
var errEmptyAccessToken = errors.New("received an empty access token") var errEmptyAccessToken = errors.New("received an empty access token")
var consentMessageFormat = ` var consentMessageFormat = `
@ -38,6 +26,7 @@ Once you have the code please copy and paste it below.
func loginWithOauth2() error { func loginWithOauth2() error {
var err error var err error
var instance string var instance string
fmt.Print("Please enter the instance URL: ") fmt.Print("Please enter the instance URL: ")
@ -82,11 +71,14 @@ func loginWithOauth2() error {
return fmt.Errorf("unable to get the access token; %w", err) return fmt.Errorf("unable to get the access token; %w", err)
} }
fmt.Printf("%+v", client.authentication) account, err := client.verifyCredentials()
if err != nil {
return fmt.Errorf("unable to verify the credentials; %w", err)
}
// validate authentication and get username for file save if err := saveAuthenticationConfig(account.Username, client.authentication); err != nil {
return fmt.Errorf("unable to save the authentication details; %w", err)
// save the authentication to a file }
return nil return nil
} }

View file

@ -6,7 +6,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"time"
) )
type RegisterRequest struct { type RegisterRequest struct {
@ -44,7 +43,7 @@ func (g *gtsClient) register() error {
path := "/api/v1/apps" path := "/api/v1/apps"
url := g.authentication.Instance + path url := g.authentication.Instance + path
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) ctx, cancel := context.WithTimeout(context.Background(), g.timeout)
defer cancel() defer cancel()
request, err := http.NewRequestWithContext(ctx, http.MethodPost, url, requestBody) request, err := http.NewRequestWithContext(ctx, http.MethodPost, url, requestBody)