checkpoint: auths are now saved to file
This commit is contained in:
parent
624cd561ed
commit
d3c67562ec
5 changed files with 209 additions and 18 deletions
58
account.go
Normal file
58
account.go
Normal 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"`
|
||||||
|
}
|
25
client.go
25
client.go
|
@ -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
117
config.go
Normal 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
|
||||||
|
}
|
24
login.go
24
login.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue