summaryrefslogtreecommitdiff
path: root/internal/platforms
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2024-10-08 11:00:32 +0300
committerPaul Buetow <paul@buetow.org>2024-10-08 11:00:32 +0300
commit8e30ad597bf200c2f3be22262364a6f4e1af1a36 (patch)
tree3eea2d66f09ad5f73ee9a65cc1077162820b335b /internal/platforms
parent2bc4585db96da4000fbf727beadfc25af64bcd4a (diff)
more on the linkedin oauth refactoring. need to test the changes, though.
Diffstat (limited to 'internal/platforms')
-rw-r--r--internal/platforms/linkedin/linkedin.go21
-rw-r--r--internal/platforms/linkedin/oauth2/oauth2.go121
2 files changed, 133 insertions, 9 deletions
diff --git a/internal/platforms/linkedin/linkedin.go b/internal/platforms/linkedin/linkedin.go
index 6205234..768def5 100644
--- a/internal/platforms/linkedin/linkedin.go
+++ b/internal/platforms/linkedin/linkedin.go
@@ -6,31 +6,34 @@ import (
"encoding/json"
"fmt"
"io"
- "log"
"net/http"
- "codeberg.org/snonux/gos/gosdir/db/platforms/linkedin/oauth2"
"codeberg.org/snonux/gos/internal/config"
"codeberg.org/snonux/gos/internal/entry"
+ "codeberg.org/snonux/gos/internal/platforms/linkedin/oauth2"
)
// TODO: Also implemebt a Text Platform output, which then laster can be
// processed by Gemtexter as a page
func Post(ctx context.Context, args config.Args, ent entry.Entry) error {
- secrets, err := oauth2.AccessToken(args)
+ content, err := ent.Content()
+ if err != nil {
+ return nil
+ }
+
+ personID, accessToken, err := oauth2.LinkedInOauth2Creds(args)
if err != err {
return err
}
- // TODO: Don't log this anymore
- log.Println("DEBUG", "Got access token", secrets)
- return nil
+
+ return post(personID, accessToken, content)
}
-func postMessage(secrets config.Secrets, message string) error {
+func post(personID, accessToken, message string) error {
const url = "https://api.linkedin.com/v2/posts"
post := map[string]interface{}{
- "author": fmt.Sprintf("urn:li:person:%s", secrets.LinkedInPesonID),
+ "author": fmt.Sprintf("urn:li:person:%s", personID),
"commentary": message,
"visibility": "PUBLIC",
"distribution": map[string]interface{}{
@@ -52,7 +55,7 @@ func postMessage(secrets config.Secrets, message string) error {
return fmt.Errorf("Error creating request: %w", err)
}
- req.Header.Add("Authorization", "Bearer "+secrets.LinkedInAccessToken)
+ req.Header.Add("Authorization", "Bearer "+accessToken)
req.Header.Set("Content-Type", "application/json")
req.Header.Add("X-RestLi-Protocol-Version", "2.0.0")
diff --git a/internal/platforms/linkedin/oauth2/oauth2.go b/internal/platforms/linkedin/oauth2/oauth2.go
new file mode 100644
index 0000000..6a692b3
--- /dev/null
+++ b/internal/platforms/linkedin/oauth2/oauth2.go
@@ -0,0 +1,121 @@
+package oauth2
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+
+ "codeberg.org/snonux/gos/internal/config"
+ "golang.org/x/oauth2"
+ "golang.org/x/oauth2/linkedin"
+)
+
+var (
+ oauthConfig *oauth2.Config
+ oauthAccessToken string
+ oauthPersonID string
+ errCh chan error
+)
+
+func getOauthPersonID(token *oauth2.Token) (string, error) {
+ const url = "https://api.linkedin.com/v2/userinfo"
+
+ req, err := http.NewRequest("GET", url, nil)
+ if err != nil {
+ return "", fmt.Errorf("Error creating request:%w", err)
+ }
+
+ req.Header.Set("Authorization", "Bearer "+token.AccessToken)
+ req.Header.Set("X-RestLi-Protocol-Version", "2.0.0")
+
+ client := &http.Client{}
+ resp, err := client.Do(req)
+ if err != nil {
+ return "", fmt.Errorf("Error making the request:%w", err)
+ }
+ defer resp.Body.Close()
+
+ body, _ := io.ReadAll(resp.Body)
+ if resp.StatusCode != http.StatusOK {
+ return "", fmt.Errorf("Failed to retrieve user profile. Status: %s\n%s\n", resp.Status, string(body))
+ }
+
+ type User struct {
+ Sub string `json:"sub"`
+ }
+ var user User
+ if err := json.Unmarshal(body, &user); err != nil {
+ return "", fmt.Errorf("Error unmarshalling JSON: %w", err)
+ }
+
+ return user.Sub, nil
+}
+
+func oauthIndexHandler(w http.ResponseWriter, r *http.Request) {
+ url := oauthConfig.AuthCodeURL("state", oauth2.AccessTypeOffline)
+ http.Redirect(w, r, url, http.StatusTemporaryRedirect)
+}
+
+func oauthCallbackHandler(w http.ResponseWriter, r *http.Request) {
+ defer close(errCh)
+ code := r.URL.Query().Get("code")
+
+ token, err := oauthConfig.Exchange(context.Background(), code)
+ if err != nil {
+ _, _ = w.Write([]byte(err.Error()))
+ errCh <- err
+ return
+ }
+ oauthAccessToken = token.AccessToken
+ _, _ = w.Write([]byte("Successfully fetched LinkedIn access token\n"))
+
+ if oauthPersonID, err = getOauthPersonID(token); err != nil {
+ _, _ = w.Write([]byte(err.Error()))
+ errCh <- err
+ return
+ }
+ _, _ = w.Write([]byte("Successfully fetched LinkedIn person ID\n"))
+}
+
+func LinkedInOauth2Creds(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
+ }
+
+ oauthConfig = &oauth2.Config{
+ ClientID: secrets.LinkedInClientID,
+ ClientSecret: secrets.LinkedInSecret,
+ RedirectURL: secrets.LinkedInRedirectURL,
+ Scopes: []string{"openid", "profile", "w_member_social"},
+ Endpoint: linkedin.Endpoint,
+ }
+ errCh := make(chan error)
+
+ http.HandleFunc("/", oauthIndexHandler)
+ http.HandleFunc("/callback", oauthCallbackHandler)
+
+ go func() {
+ log.Println("Listening on http://localhost:8080 for LinkedIn oauth2")
+ if err := http.ListenAndServe(":8080", nil); err != nil {
+ errCh <- err
+ }
+ }()
+
+ var errs error
+ for err := range errCh {
+ errs = errors.Join(errs, err)
+ }
+ if errs != nil {
+ return "", "", errs
+ }
+
+ secrets.MastodonAccessToken = oauthAccessToken
+ secrets.LinkedInPersonID = oauthPersonID
+ return oauthPersonID, oauthAccessToken, secrets.WriteToDisk(args.SecretsConfigPath)
+}