diff options
| author | Paul Buetow <paul@buetow.org> | 2024-10-08 11:00:32 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2024-10-08 11:00:32 +0300 |
| commit | 8e30ad597bf200c2f3be22262364a6f4e1af1a36 (patch) | |
| tree | 3eea2d66f09ad5f73ee9a65cc1077162820b335b /internal/platforms | |
| parent | 2bc4585db96da4000fbf727beadfc25af64bcd4a (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.go | 21 | ||||
| -rw-r--r-- | internal/platforms/linkedin/oauth2/oauth2.go | 121 |
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) +} |
