Make r_basicprofile opt-in via LINKEDIN_EXTRA_SCOPES so self-hosted users
unblock by default and ops with legacy/enterprise products keep working.
Why
---
LinkedIn rejects OAuth authorize requests with a generic "Bummer,
something went wrong" page when an app asks for a scope it can't grant.
`r_basicprofile` is a legacy scope deprecated in 2018; new LinkedIn dev
apps don't have it, so every self-hosted user hits the rejection
immediately on `/connect/linkedin`.
The two products LinkedIn actually grants to standard apps today are:
- Sign In with LinkedIn using OpenID Connect → `openid profile email`
- Share on LinkedIn → `w_member_social`
That set is enough for the connect flow. The only piece of data
`r_basicprofile` was buying us is `/v2/me`'s `vanityName` (pretty
`linkedin.com/in/<slug>`). `fetchVanityName()` already handles HTTP
failure gracefully (returns null), and the only downstream consumer —
`LinkedInPagePublisher`'s post-URL builder — already falls back to a
numeric `linkedin.com/feed/update/<id>` URL when `$account->username`
is null.
Backward compatibility
----------------------
Ops with legacy or enterprise LinkedIn products approved on their dev
app (so they DO have `r_basicprofile`) can opt back in via env:
LINKEDIN_EXTRA_SCOPES=r_basicprofile
`LinkedInController::resolveScopes()` merges this comma-separated list
into the default scope array. The connect flow's `Socialite::scopes()`
call then includes the legacy scope, preserving the pre-PR behaviour
end-to-end (including `vanityName` lookup).
Net effect for users without `r_basicprofile`:
- Connect flow works (was previously rejected by LinkedIn).
- Posts publish exactly the same way.
- Generated post URLs use the numeric form instead of the vanity slug.
Tests
-----
- `linkedin connect requests the default scope set when LINKEDIN_EXTRA_SCOPES is unset`
- `linkedin connect appends LINKEDIN_EXTRA_SCOPES to the default scope set`
- Existing `splits comma-separated approvedScopes` fixture updated to
match the new default set.
168 lines
4 KiB
Text
168 lines
4 KiB
Text
APP_NAME="TryPost"
|
|
APP_ENV=local
|
|
APP_KEY=
|
|
APP_DEBUG=true
|
|
APP_URL=http://localhost:8000
|
|
|
|
# Self-hosted mode (skips payment requirements)
|
|
SELF_HOSTED=true
|
|
|
|
TELESCOPE_ENABLED=false
|
|
|
|
APP_LOCALE=en
|
|
APP_FALLBACK_LOCALE=en
|
|
APP_FAKER_LOCALE=en_US
|
|
|
|
APP_MAINTENANCE_DRIVER=file
|
|
|
|
BCRYPT_ROUNDS=12
|
|
|
|
LOG_CHANNEL=stack
|
|
LOG_STACK=single
|
|
LOG_DEPRECATIONS_CHANNEL=null
|
|
LOG_LEVEL=debug
|
|
|
|
# Database — service name "pgsql" inside the Compose network.
|
|
DB_CONNECTION=pgsql
|
|
DB_HOST=pgsql
|
|
DB_PORT=5432
|
|
DB_DATABASE=trypost
|
|
DB_USERNAME=postgres
|
|
DB_PASSWORD=password
|
|
|
|
# Session
|
|
SESSION_DRIVER=database
|
|
SESSION_LIFETIME=1440
|
|
SESSION_ENCRYPT=false
|
|
SESSION_PATH=/
|
|
SESSION_DOMAIN=null
|
|
|
|
# Broadcasting, Queue, Cache — all Redis-backed in Docker.
|
|
BROADCAST_CONNECTION=reverb
|
|
QUEUE_CONNECTION=redis
|
|
CACHE_STORE=redis
|
|
|
|
# File Storage — local-disk by default; switch to s3/r2 for production.
|
|
FILESYSTEM_DISK=local
|
|
|
|
# Redis — service name "redis" inside the Compose network.
|
|
REDIS_HOST=redis
|
|
REDIS_PASSWORD=null
|
|
REDIS_PORT=6379
|
|
|
|
# Mail — service name "mailpit" inside the Compose network. UI at :8025.
|
|
MAIL_MAILER=smtp
|
|
MAIL_HOST=mailpit
|
|
MAIL_PORT=1025
|
|
MAIL_USERNAME=null
|
|
MAIL_PASSWORD=null
|
|
MAIL_ENCRYPTION=null
|
|
MAIL_FROM_ADDRESS="hello@example.com"
|
|
MAIL_FROM_NAME="${APP_NAME}"
|
|
|
|
# Reverb (WebSockets) — host=localhost is browser-perspective via host port-forward.
|
|
REVERB_APP_ID=1001
|
|
REVERB_APP_KEY=trypost-reverb-key
|
|
REVERB_APP_SECRET=trypost-reverb-secret
|
|
REVERB_HOST=localhost
|
|
REVERB_PORT=8080
|
|
REVERB_SCHEME=http
|
|
|
|
# AWS S3 (set FILESYSTEM_DISK=s3)
|
|
AWS_ACCESS_KEY_ID=
|
|
AWS_SECRET_ACCESS_KEY=
|
|
AWS_DEFAULT_REGION=us-east-1
|
|
AWS_BUCKET=
|
|
AWS_URL=
|
|
|
|
# Cloudflare R2 (set FILESYSTEM_DISK=r2)
|
|
R2_ACCESS_KEY_ID=
|
|
R2_SECRET_ACCESS_KEY=
|
|
R2_ENDPOINT=
|
|
R2_REGION=auto
|
|
R2_BUCKET=
|
|
R2_URL=
|
|
|
|
# ============================================
|
|
# Social Platform Credentials (optional in dev)
|
|
# ============================================
|
|
LINKEDIN_CLIENT_ID=
|
|
LINKEDIN_CLIENT_SECRET=
|
|
LINKEDIN_CLIENT_REDIRECT="${APP_URL}/accounts/linkedin/callback"
|
|
LINKEDIN_PAGE_CLIENT_REDIRECT="${APP_URL}/accounts/linkedin-page/callback"
|
|
# Optional: comma-separated extra OAuth scopes appended to the personal
|
|
# LinkedIn connect flow. Use this if your LinkedIn dev app has legacy or
|
|
# enterprise products approved — e.g. `r_basicprofile` re-enables
|
|
# vanityName lookup via /v2/me. Leave empty for the safe default set.
|
|
# LINKEDIN_EXTRA_SCOPES=r_basicprofile
|
|
|
|
X_CLIENT_ID=
|
|
X_CLIENT_SECRET=
|
|
X_CLIENT_REDIRECT="${APP_URL}/accounts/x/callback"
|
|
|
|
TIKTOK_CLIENT_ID=
|
|
TIKTOK_CLIENT_SECRET=
|
|
TIKTOK_CLIENT_REDIRECT="${APP_URL}/accounts/tiktok/callback"
|
|
|
|
FACEBOOK_CLIENT_ID=
|
|
FACEBOOK_CLIENT_SECRET=
|
|
FACEBOOK_CLIENT_REDIRECT="${APP_URL}/accounts/facebook/callback"
|
|
|
|
INSTAGRAM_CLIENT_ID=
|
|
INSTAGRAM_CLIENT_SECRET=
|
|
INSTAGRAM_CLIENT_REDIRECT="${APP_URL}/accounts/instagram/callback"
|
|
|
|
THREADS_CLIENT_ID=
|
|
THREADS_CLIENT_SECRET=
|
|
THREADS_CLIENT_REDIRECT="${APP_URL}/accounts/threads/callback"
|
|
|
|
GOOGLE_AUTH_ENABLED=false
|
|
GOOGLE_CLIENT_ID=
|
|
GOOGLE_CLIENT_SECRET=
|
|
GOOGLE_CLIENT_REDIRECT="${APP_URL}/accounts/youtube/callback"
|
|
GOOGLE_AUTH_CALLBACK="${APP_URL}/auth/google/callback"
|
|
|
|
GITHUB_AUTH_ENABLED=false
|
|
GITHUB_CLIENT_ID=
|
|
GITHUB_CLIENT_SECRET=
|
|
GITHUB_AUTH_CALLBACK="${APP_URL}/auth/github/callback"
|
|
|
|
PINTEREST_CLIENT_ID=
|
|
PINTEREST_CLIENT_SECRET=
|
|
PINTEREST_CLIENT_REDIRECT="${APP_URL}/accounts/pinterest/callback"
|
|
|
|
# AI Services
|
|
OPENAI_API_KEY=
|
|
ANTHROPIC_API_KEY=
|
|
GEMINI_API_KEY=
|
|
ELEVENLABS_API_KEY=
|
|
|
|
AI_TEXT_PROVIDER=openai
|
|
AI_TEXT_MODEL=gpt-5.4
|
|
AI_IMAGE_PROVIDER=openai
|
|
AI_AUDIO_PROVIDER=elevenlabs
|
|
|
|
# Telemetry — disabled by default in Docker dev.
|
|
NIGHTWATCH_ENABLED=false
|
|
NIGHTWATCH_TOKEN=
|
|
|
|
# Media Services
|
|
UNSPLASH_ACCESS_KEY=
|
|
UNSPLASH_SECRET_KEY=
|
|
GIPHY_API_KEY=
|
|
|
|
# Analytics (optional)
|
|
GTM_ID=
|
|
POSTHOG_ENABLED=false
|
|
POSTHOG_API_KEY=
|
|
POSTHOG_HOST=https://us.i.posthog.com
|
|
|
|
# Vite
|
|
VITE_APP_NAME="${APP_NAME}"
|
|
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
|
|
VITE_REVERB_HOST="${REVERB_HOST}"
|
|
VITE_REVERB_PORT="${REVERB_PORT}"
|
|
VITE_REVERB_SCHEME="${REVERB_SCHEME}"
|
|
VITE_POSTHOG_ENABLED="${POSTHOG_ENABLED}"
|
|
VITE_POSTHOG_API_KEY="${POSTHOG_API_KEY}"
|
|
VITE_POSTHOG_HOST="${POSTHOG_HOST}"
|