Skip to content

fix(linkedin,pinterest): split CSV/space-joined OAuth scopes before saving#35

Merged
paulocastellano merged 2 commits into
mainfrom
fix/linkedin-scopes-parsing
May 14, 2026
Merged

fix(linkedin,pinterest): split CSV/space-joined OAuth scopes before saving#35
paulocastellano merged 2 commits into
mainfrom
fix/linkedin-scopes-parsing

Conversation

@paulocastellano
Copy link
Copy Markdown
Contributor

@paulocastellano paulocastellano commented May 14, 2026

Context

Users connecting LinkedIn (and Pinterest) and posting some time later were hitting:

```
Missing permissions: w_member_social. Please reconnect your account.
```

…even though the LinkedIn account actually had the scope granted, and the daily `social:check-connections` reported the account as healthy.

Root cause

LinkedIn returns the granted `scope` field comma-separated and Pinterest returns it space-separated, but Socialite's scope splitter for each provider doesn't match what they actually return. The net effect: `approvedScopes` is saved as a single-element array containing the entire list as one string:

```
LinkedIn: ['email,openid,profile,r_basicprofile,w_member_social']
Pinterest: ['boards:read boards:write pins:read pins:write user_accounts:read']
```

When `PublishToSocialPlatform` runs:

```php
array_diff(['w_member_social'], ['email,openid,profile,r_basicprofile,w_member_social'])
```

`array_diff` does exact string compare. `'w_member_social'` doesn't equal the CSV string, so it reports it as missing → publish blocked.

Confirmed by inspecting both DB rows and live OAuth response logs.

Fix

Inline re-split at each callback before saving. Provider-specific quirks stay in their own controllers — no shared helper, no centralized abstraction.

`LinkedInController::callback`:
```php
'scopes' => explode(',', implode(',', $socialUser->approvedScopes)),
```

`PinterestController::callback`:
```php
'scopes' => explode(' ', implode(' ', $socialUser->approvedScopes)),
```

The `implode + explode` pattern handles both possible shapes: a single-element CSV (today's bug) or already-split multi-element (future-proof if Socialite ever changes).

Not affected

  • TikTok confirmed via debug log: returns 6 properly-split elements. Code unchanged.
  • LinkedInPage doesn't write the `scopes` column at all.
  • Facebook / Instagram / Threads / YouTube / Mastodon / X / Bluesky: confirmed via DB inspection — all save properly-split arrays.

Existing broken accounts in DB

Will be fixed manually via direct SQL (3 LinkedIn rows + 1 Pinterest row). No migration in this PR.

Test plan

  • `php artisan test --compact --parallel` — 1511 passed, 2 skipped, 0 failed (+2 over main)
  • New: `linkedin oauth callback splits comma-separated approvedScopes before saving`
  • New: `pinterest oauth callback splits space-separated approvedScopes before saving`
  • Manual: reconnect a LinkedIn account → DB has 5 separate scope entries
  • Manual: reconnect a Pinterest account → DB has 5 separate scope entries
  • Manual: attempt to post to a freshly-reconnected LinkedIn account → publish succeeds

…aving

LinkedIn and Pinterest's OAuth providers return the granted scope list
joined by comma (LinkedIn) or space-in-one-element (Pinterest), but
Socialite's scope splitter doesn't match either, so 'approvedScopes'
lands as a single-element array containing the whole list:

  LinkedIn:  ['email,openid,profile,r_basicprofile,w_member_social']
  Pinterest: ['boards:read boards:write pins:read pins:write user_accounts:read']

That breaks the publish-time scope check in PublishToSocialPlatform
(array_diff does exact string compare), surfacing as
'Missing permissions: w_member_social. Please reconnect your account'
even though the scopes were actually granted at the provider.

Fix is inline at each callback — re-split before saving. Each provider
has its own quirk (LinkedIn = comma, Pinterest = space), so each
controller handles its own separator.

Tests added: callback splits the joined approvedScopes into individual
tokens for both providers.
@paulocastellano paulocastellano force-pushed the fix/linkedin-scopes-parsing branch from c87497d to 49ccfe8 Compare May 14, 2026 13:55
@paulocastellano paulocastellano changed the title fix(linkedin): split comma-separated OAuth scopes + centralize scope config fix(linkedin,pinterest): split CSV/space-joined OAuth scopes before saving May 14, 2026
Adds an explicit test asserting that when a workspace has multiple
LinkedIn users (e.g., the owner plus a client teammate), syncing
tokens from one user's accounts never touches accounts admin'd by a
different LinkedIn user — even though all live in the same workspace.

The behavior is already correct by virtue of the
`admin_user_id` / `platform_user_id` match in the synchronizer's
query, but the property is now part of the test contract instead of
emergent.
@paulocastellano paulocastellano merged commit 3845737 into main May 14, 2026
2 checks passed
@paulocastellano paulocastellano deleted the fix/linkedin-scopes-parsing branch May 14, 2026 14:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant