fix(auth): load projects for project-scoped OAuth#3552
Conversation
Generated-By: PostHog Code Task-Id: 07b377f9-4583-463c-b256-cc29621375e6
|
Merging to
After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here |
|
React Doctor found no issues in the changed files. 🎉 Reviewed by React Doctor for commit |
Generated-By: PostHog Code Task-Id: 07b377f9-4583-463c-b256-cc29621375e6
Generated-By: PostHog Code Task-Id: 07b377f9-4583-463c-b256-cc29621375e6
|
CI follow-up: all code-owned checks on this PR passed (build, unit/integration/E2E, typecheck, quality, and security scans). The remaining |
|
Reviews (1): Last reviewed commit: "Merge branch 'main' into posthog-code/fi..." | Re-trigger Greptile |
| scopedOrgIds.length > 0 | ||
| ? await this.buildOrgProjectsMap( | ||
| tokenResponse.access_token, | ||
| options.cloudRegion, | ||
| scopedOrgIds, | ||
| previousMap, | ||
| ) | ||
| : scopedProjectIds.length > 0 |
There was a problem hiding this comment.
Mixed Scopes Skip Project Hydration
When a token contains both scoped_organizations and scoped_teams, this branch uses only the organization path. If the organization endpoint rejects the project-scoped token or omits its team-only projects, those authorized projects disappear from the picker even though their IDs were supplied in scoped_teams.
Rule Used: When implementing new features, ensure that owners... (source)
Learned From
PostHog/posthog#31236
| const resolvedCurrentOrgId = | ||
| currentOrgId && orgProjectsMap[currentOrgId] | ||
| ? currentOrgId | ||
| : (Object.keys(orgProjectsMap)[0] ?? currentOrgId); |
There was a problem hiding this comment.
Organization And Project Can Diverge
For scoped projects spanning multiple organizations, pickInitialProjectId can restore a preferred project from one organization while this independent fallback chooses the first organization in the map. The session then exposes a current organization that does not own the current project, so the picker and later organization-scoped operations can target different organizations.
|
|
||
| const { orgId, project } = result.data; | ||
| const existing = map[orgId]; | ||
| map[orgId] = { |
There was a problem hiding this comment.
Permanent Failure Silently Drops Scope
If one scoped project returns a non-retryable response such as 403, 404, or malformed successful JSON, this path omits it without marking the map incomplete. The session is then committed with recovery disabled, so that token-provided project remains missing until the user authenticates again.
Generated-By: PostHog Code Task-Id: 07b377f9-4583-463c-b256-cc29621375e6
Problem
Project-scoped OAuth tokens expose authorized project IDs in
scoped_teams, but PostHog Code discarded that field and tried to load projects through the organization endpoint. When the backend rejects project-scoped tokens on organization endpoints, the session remains authenticated with “No project selected.”This follows #3279, merged on July 8, 2026. That fix correctly handled empty
scoped_organizationsby falling back to the user’s current organization, but it depended on the organization endpoint accepting the team-scoped token. The direct scoped-project path removes that dependency.Why: Local development should work when a developer grants project-level access instead of organization-level access during OAuth authorization.
Changes
scoped_teamsfrom OAuth token responses and hydrate those projects directly through project-scoped endpoints.How did you test this?
pnpm build:depspnpm --filter @posthog/core exec vitest run src/auth/auth.test.ts src/auth/oauth.schemas.test.tspnpm --filter @posthog/core typecheckpnpm exec biome lint packages/coreAutomatic notifications
Created with PostHog Code