summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-17 22:03:10 +0200
committerPaul Buetow <paul@buetow.org>2026-02-17 22:03:10 +0200
commit5f53f241189af3fceb26395af383809cd4fa3bf0 (patch)
tree4ffabc358ec85ed1da425f72e1357e80f340b2c1 /internal
parenta8cb28d2257cff652fc4f8a9768b66b9d69fd68a (diff)
fix: improve LinkedIn API posting and authenticationv1.2.4
- Add token validation before using cached credentials to prevent stale token issues - Add proper error handling for image upload initialization with helpful 426 messages - Skip data URI images instead of attempting to download them - Update default LinkedIn API version to 202601 (January 2026) - latest active version - Fix re-authentication flow for expired tokens Amp-Thread-ID: https://ampcode.com/threads/T-019c6d28-4526-7738-b593-9bd584baa478 Co-authored-by: Amp <amp@ampcode.com>
Diffstat (limited to 'internal')
-rw-r--r--internal/platforms/linkedin/linkedin.go27
-rw-r--r--internal/platforms/linkedin/oauth2/oauth2.go8
-rw-r--r--internal/platforms/linkedin/preview.go6
-rw-r--r--internal/platforms/linkedin/testdata/fuzz/FuzzLinkedInURLExtract/5ffd3a9d584973772
-rw-r--r--internal/platforms/linkedin/testdata/fuzz/FuzzLinkedInURLExtract/771e938e4458e9832
-rw-r--r--internal/platforms/linkedin/testdata/fuzz/FuzzLinkedInURLExtract/b0118fa98fb2891d2
-rw-r--r--internal/version.go2
7 files changed, 43 insertions, 6 deletions
diff --git a/internal/platforms/linkedin/linkedin.go b/internal/platforms/linkedin/linkedin.go
index 11291fb..16688d5 100644
--- a/internal/platforms/linkedin/linkedin.go
+++ b/internal/platforms/linkedin/linkedin.go
@@ -27,9 +27,10 @@ func addCommonHeaders(req *http.Request, accessToken, liVersion string) {
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-RestLi-Protocol-Version", "2.0.0")
- if liVersion != "" {
- req.Header.Set("LinkedIn-Version", liVersion)
+ if liVersion == "" {
+ liVersion = "202601" // Default to latest stable version
}
+ req.Header.Set("LinkedIn-Version", liVersion)
}
func Post(ctx context.Context, args config.Args, sizeLimit int, en entry.Entry) error {
@@ -147,7 +148,7 @@ func postMessageToLinkedInAPI(ctx context.Context, personID, accessToken, conten
} else if resp.StatusCode == http.StatusUpgradeRequired {
// 426 often indicates a non-active LinkedIn-Version header.
// Provide a clear hint to configure a valid version.
- err = fmt.Errorf("%w; LinkedIn API version likely inactive. Set an active 'LinkedInVersion' in config (e.g. 202502) or remove to use default. Response: %s", err, string(body))
+ err = fmt.Errorf("%w; LinkedIn API version likely inactive. Set an active 'LinkedInVersion' in config (e.g. 202601) or remove to use default. Response: %s", err, string(body))
}
}
return err
@@ -191,6 +192,24 @@ func initializeImageUpload(ctx context.Context, personURN, accessToken string, l
}
defer resp.Body.Close()
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return "", "", fmt.Errorf("error reading response: %w", err)
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ err := fmt.Errorf("image upload initialization failed. Status: %s\n%s\n",
+ resp.Status, string(body))
+ if resp.StatusCode == http.StatusUnauthorized {
+ err = errors.Join(err, errUnauthorized)
+ } else if resp.StatusCode == http.StatusUpgradeRequired {
+ // 426 often indicates a non-active LinkedIn-Version header.
+ // Provide a clear hint to configure a valid version.
+ err = fmt.Errorf("%w; LinkedIn API version likely inactive. Set an active 'LinkedInVersion' in config (e.g. 202601) or remove to use default. Response: %s", err, string(body))
+ }
+ return "", "", err
+ }
+
type InitializeUploadResponse struct {
Value struct {
UploadURL string `json:"uploadUrl"`
@@ -198,7 +217,7 @@ func initializeImageUpload(ctx context.Context, personURN, accessToken string, l
} `json:"value"`
}
var response InitializeUploadResponse
- if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
+ if err := json.Unmarshal(body, &response); err != nil {
return "", "", fmt.Errorf("error decoding response: %w", err)
}
diff --git a/internal/platforms/linkedin/oauth2/oauth2.go b/internal/platforms/linkedin/oauth2/oauth2.go
index 8fe25ab..45cb9b7 100644
--- a/internal/platforms/linkedin/oauth2/oauth2.go
+++ b/internal/platforms/linkedin/oauth2/oauth2.go
@@ -93,7 +93,13 @@ func oauthCallbackHandler(w http.ResponseWriter, r *http.Request) {
func LinkedInCreds(ctx context.Context, args config.Args) (string, string, error) {
conf := args.Config
if conf.LinkedInAccessToken != "" && conf.LinkedInPersonID != "" {
- return conf.LinkedInPersonID, conf.LinkedInAccessToken, nil
+ // Validate cached token before using it
+ token := &oauth2.Token{AccessToken: conf.LinkedInAccessToken}
+ if _, err := getOauthPersonID(token); err == nil {
+ return conf.LinkedInPersonID, conf.LinkedInAccessToken, nil
+ }
+ // Cached token is invalid, clear it to trigger re-auth
+ conf.LinkedInAccessToken = ""
}
oauthConfig = &oauth2.Config{
diff --git a/internal/platforms/linkedin/preview.go b/internal/platforms/linkedin/preview.go
index a583c7a..125a4bf 100644
--- a/internal/platforms/linkedin/preview.go
+++ b/internal/platforms/linkedin/preview.go
@@ -73,6 +73,12 @@ func (p preview) Thumbnail() (string, bool) {
}
func (p preview) DownloadImage(destPath string) (string, error) {
+ // Skip data URIs - they can't be downloaded and don't provide meaningful images
+ if u, err := url.Parse(p.thumbnailURL); err == nil && u.Scheme == "data" {
+ colour.Infoln("Skipping data URI image, using article metadata instead")
+ return "", nil
+ }
+
if err := oi.EnsureDir(destPath); err != nil {
return "", err
}
diff --git a/internal/platforms/linkedin/testdata/fuzz/FuzzLinkedInURLExtract/5ffd3a9d58497377 b/internal/platforms/linkedin/testdata/fuzz/FuzzLinkedInURLExtract/5ffd3a9d58497377
new file mode 100644
index 0000000..2eecaf6
--- /dev/null
+++ b/internal/platforms/linkedin/testdata/fuzz/FuzzLinkedInURLExtract/5ffd3a9d58497377
@@ -0,0 +1,2 @@
+go test fuzz v1
+string("\n\n\n")
diff --git a/internal/platforms/linkedin/testdata/fuzz/FuzzLinkedInURLExtract/771e938e4458e983 b/internal/platforms/linkedin/testdata/fuzz/FuzzLinkedInURLExtract/771e938e4458e983
new file mode 100644
index 0000000..ee3f339
--- /dev/null
+++ b/internal/platforms/linkedin/testdata/fuzz/FuzzLinkedInURLExtract/771e938e4458e983
@@ -0,0 +1,2 @@
+go test fuzz v1
+string("0")
diff --git a/internal/platforms/linkedin/testdata/fuzz/FuzzLinkedInURLExtract/b0118fa98fb2891d b/internal/platforms/linkedin/testdata/fuzz/FuzzLinkedInURLExtract/b0118fa98fb2891d
new file mode 100644
index 0000000..190e0f4
--- /dev/null
+++ b/internal/platforms/linkedin/testdata/fuzz/FuzzLinkedInURLExtract/b0118fa98fb2891d
@@ -0,0 +1,2 @@
+go test fuzz v1
+string("0 0")
diff --git a/internal/version.go b/internal/version.go
index f54de26..3980280 100644
--- a/internal/version.go
+++ b/internal/version.go
@@ -6,7 +6,7 @@ import (
"codeberg.org/snonux/gos/internal/table"
)
-const versionStr = "v1.2.3"
+const versionStr = "v1.2.4"
func printVersion() {
table.New().