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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type gtsClient struct {
|
||||
authentication Authentication
|
||||
httpClient http.Client
|
||||
userAgent string
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func newGtsClient(authentication Authentication) *gtsClient {
|
||||
|
@ -19,11 +22,33 @@ func newGtsClient(authentication Authentication) *gtsClient {
|
|||
authentication: authentication,
|
||||
httpClient: httpClient,
|
||||
userAgent: userAgent,
|
||||
timeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
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 {
|
||||
request.Header.Set("Content-Type", "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"
|
||||
)
|
||||
|
||||
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 consentMessageFormat = `
|
||||
|
@ -38,6 +26,7 @@ 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: ")
|
||||
|
@ -82,11 +71,14 @@ func loginWithOauth2() error {
|
|||
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
|
||||
|
||||
// save the authentication to a file
|
||||
if err := saveAuthenticationConfig(account.Username, client.authentication); err != nil {
|
||||
return fmt.Errorf("unable to save the authentication details; %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RegisterRequest struct {
|
||||
|
@ -44,7 +43,7 @@ func (g *gtsClient) register() error {
|
|||
path := "/api/v1/apps"
|
||||
url := g.authentication.Instance + path
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), g.timeout)
|
||||
defer cancel()
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodPost, url, requestBody)
|
||||
|
|
Loading…
Reference in a new issue