summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2024-10-09 11:25:49 +0300
committerPaul Buetow <paul@buetow.org>2024-10-09 11:25:49 +0300
commit6622cc6402df56dd416e3ca3894d5b208fa26077 (patch)
treed49582c4bb2216000b01ea7d8eb2ef37ba784977
parentfba8665dcc22d11f8ac7b7583701951dc4d9544f (diff)
can re-use access token for linkedin oauth
-rw-r--r--cmd/gos/main.go2
-rw-r--r--internal/config/args.go1
-rw-r--r--internal/config/secrets.go7
-rw-r--r--internal/entry/entry.go1
-rw-r--r--internal/platforms/linkedin/linkedin.go2
-rw-r--r--internal/platforms/linkedin/oauth2/oauth2.go62
6 files changed, 67 insertions, 8 deletions
diff --git a/cmd/gos/main.go b/cmd/gos/main.go
index 5b98386..6ea4995 100644
--- a/cmd/gos/main.go
+++ b/cmd/gos/main.go
@@ -20,6 +20,7 @@ func main() {
dry := flag.Bool("dry", false, "Dry run")
version := flag.Bool("version", false, "Display version")
gosDir := flag.String("gosDir", "./gosdir", "Gos' directory")
+ browser := flag.String("browser", "firefox", "OAuth2 browser")
secretsConfigPath := filepath.Join(os.Getenv("HOME"), ".config/gos/gosec.json")
secretsConfigPath = *flag.String("secretsConfig", secretsConfigPath, "Gos' secret config")
platforms := flag.String("platforms", "Mastodon,LinkedIn", "Platforms enabled")
@@ -40,6 +41,7 @@ func main() {
Lookback: time.Duration(*lookback) * time.Hour * 24,
SecretsConfigPath: secretsConfigPath,
Secrets: secrets,
+ OAuth2Browser: *browser,
}
if err := args.Validate(); err != nil {
diff --git a/internal/config/args.go b/internal/config/args.go
index 601a2d1..3bfa78e 100644
--- a/internal/config/args.go
+++ b/internal/config/args.go
@@ -17,6 +17,7 @@ type Args struct {
Lookback time.Duration
SecretsConfigPath string
Secrets Secrets
+ OAuth2Browser string
}
func (a Args) Validate() error {
diff --git a/internal/config/secrets.go b/internal/config/secrets.go
index dc3f8e7..6ed1c1f 100644
--- a/internal/config/secrets.go
+++ b/internal/config/secrets.go
@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"io"
+ "log"
"os"
)
@@ -15,9 +16,9 @@ type Secrets struct {
LinkedInSecret string
LinkedInRedirectURL string
// Will be updated by gos automatically, after successful oauth2
- LinkedInAccessToken string `json:"LinedInAccessToken,omitempty"`
+ LinkedInAccessToken string `json:"LinkedInAccessToken,omitempty"`
// Will be updated by gos automatically, after successful oauth2
- LinkedInPersonID string `json:"LinedInPersonID,omitempty"`
+ LinkedInPersonID string `json:"LinkedInPersonID,omitempty"`
}
func NewSecrets(configPath string) (Secrets, error) {
@@ -41,6 +42,8 @@ func NewSecrets(configPath string) (Secrets, error) {
}
func (s Secrets) WriteToDisk(configPath string) error {
+ log.Println("Writing", configPath)
+
bytes, err := json.MarshalIndent(s, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal JSON: %w", err)
diff --git a/internal/entry/entry.go b/internal/entry/entry.go
index 7bb4930..0874e64 100644
--- a/internal/entry/entry.go
+++ b/internal/entry/entry.go
@@ -95,6 +95,7 @@ func (e *Entry) MarkPosted() error {
if e.State == Posted {
return errors.New("entry is already posted")
}
+ // TODO: Also update the timestamp to reflect the posting time in the file path.
if err := os.Rename(e.Path, strings.TrimSuffix(e.Path, ".queued")+".posted"); err != nil {
return err
}
diff --git a/internal/platforms/linkedin/linkedin.go b/internal/platforms/linkedin/linkedin.go
index da1ce0d..5b07077 100644
--- a/internal/platforms/linkedin/linkedin.go
+++ b/internal/platforms/linkedin/linkedin.go
@@ -21,7 +21,7 @@ func Post(ctx context.Context, args config.Args, ent entry.Entry) error {
return nil
}
- personID, accessToken, err := oauth2.LinkedInOAuth2Creds(args)
+ personID, accessToken, err := oauth2.LinkedInCreds(args)
if err != err {
return err
}
diff --git a/internal/platforms/linkedin/oauth2/oauth2.go b/internal/platforms/linkedin/oauth2/oauth2.go
index e800bac..bbc60fb 100644
--- a/internal/platforms/linkedin/oauth2/oauth2.go
+++ b/internal/platforms/linkedin/oauth2/oauth2.go
@@ -8,6 +8,9 @@ import (
"io"
"log"
"net/http"
+ "os/exec"
+ "runtime"
+ "time"
"codeberg.org/snonux/gos/internal/config"
"golang.org/x/oauth2"
@@ -55,6 +58,10 @@ func getOauthPersonID(token *oauth2.Token) (string, error) {
return user.Sub, nil
}
+func upHandler(w http.ResponseWriter, r *http.Request) {
+ _, _ = w.Write([]byte("I am up!\n"))
+}
+
func oauthIndexHandler(w http.ResponseWriter, r *http.Request) {
url := oauthConfig.AuthCodeURL("state", oauth2.AccessTypeOffline)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
@@ -64,6 +71,8 @@ func oauthCallbackHandler(w http.ResponseWriter, r *http.Request) {
defer close(errCh)
code := r.URL.Query().Get("code")
+ log.Println("Exchanging OAuth2 token")
+ // TODO: Insert the propper context
token, err := oauthConfig.Exchange(context.Background(), code)
if err != nil {
_, _ = w.Write([]byte(err.Error()))
@@ -81,11 +90,11 @@ func oauthCallbackHandler(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("Successfully fetched LinkedIn person ID\n"))
}
-func LinkedInOAuth2Creds(args config.Args) (string, string, error) {
+func LinkedInCreds(args config.Args) (string, string, error) {
secrets := args.Secrets
if secrets.LinkedInAccessToken != "" && secrets.LinkedInPersonID != "" {
// TODO: Check, whether the access token is still valid. If not, get a new one.
- return secrets.LinkedInPersonID, secrets.MastodonAccessToken, nil
+ return secrets.LinkedInPersonID, secrets.LinkedInAccessToken, nil
}
oauthConfig = &oauth2.Config{
@@ -95,18 +104,27 @@ func LinkedInOAuth2Creds(args config.Args) (string, string, error) {
Scopes: []string{"openid", "profile", "w_member_social"},
Endpoint: linkedin.Endpoint,
}
- errCh = make(chan error)
+ errCh = make(chan error, 10)
http.HandleFunc("/", oauthIndexHandler)
http.HandleFunc("/callback", oauthCallbackHandler)
+ http.HandleFunc("/up", upHandler)
- log.Println("Listening on http://localhost:8080 for LinkedIn oauth2")
+ log.Println("Listening on http://localhost:8080 for LinkedIn OAuth2")
go func() {
if err := http.ListenAndServe(":8080", nil); err != nil {
errCh <- err
}
}()
+ if err := waitUntilURLIsReachable("http://localhost:8080/up"); err != nil {
+ return "", "", err
+ }
+
+ if err := openURLInFirefox(args.OAuth2Browser, "http://localhost:8080"); err != nil {
+ return "", "", err
+ }
+
var errs error
for err := range errCh {
errs = errors.Join(errs, err)
@@ -115,7 +133,41 @@ func LinkedInOAuth2Creds(args config.Args) (string, string, error) {
return "", "", errs
}
- secrets.MastodonAccessToken = oauthAccessToken
+ secrets.LinkedInAccessToken = oauthAccessToken
secrets.LinkedInPersonID = oauthPersonID
return oauthPersonID, oauthAccessToken, secrets.WriteToDisk(args.SecretsConfigPath)
}
+
+func openURLInFirefox(browser, url string) error {
+ log.Println("Opening", url, "in", browser)
+ switch runtime.GOOS {
+ case "windows":
+ cmd := exec.Command("cmd", "/C", "start", browser, url)
+ return cmd.Start()
+ case "darwin":
+ cmd := exec.Command("open", "-a", browser, url)
+ return cmd.Start()
+ default:
+ // Linux and other Unix like (e.g. *BSDs)
+ cmd := exec.Command(browser, url)
+ return cmd.Start()
+ }
+}
+
+func waitUntilURLIsReachable(url string) error {
+ var counter int
+ for counter < 10 {
+ counter++
+ time.Sleep(1 * time.Second)
+ resp, err := http.Get(url)
+
+ if err != nil {
+ log.Printf("URL is not reachable: %v\n", err)
+ } else {
+ log.Printf("URL is reachable: %s - Status Code: %d\n", url, resp.StatusCode)
+ resp.Body.Close()
+ return nil
+ }
+ }
+ return fmt.Errorf("%s not reachable after %d tries", url, counter)
+}