From dbc033b29b3b292046b219029960fe631b70b065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Thu, 14 May 2026 12:17:17 +0200 Subject: [PATCH] fix(auth): exclude /authorize from MinimalMetadataGater loading gate (#20572) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes the blank `/authorize` page reported when a user reopens the OAuth consent screen on `app.twenty.com` after a prior multi-workspace SSO sign-in. ### Reproduction 1. ChatGPT (or any MCP client) opens `https://app.twenty.com/authorize?client_id=…` while signed out. 2. User picks "Continue with Google", lands in workspace selection, picks a workspace, authorizes the app. Works. 3. Some time later, ChatGPT re-opens `https://app.twenty.com/authorize?client_id=…`. 4. **Observed:** fully blank page, no console errors. Deleting the `tokenPair` cookie unblocks it. ### Root cause The multi-workspace social-SSO branch ([auth.service.ts:988-1011](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/auth/services/auth.service.ts#L988-L1011)) lands the user on `app.twenty.com/welcome?tokenPair=…` with a workspace-agnostic token. `SignInUpGlobalScopeFormEffect` writes that into the host-scoped `tokenPair` cookie on `app.twenty.com`, and nothing clears it after the user proceeds to a workspace subdomain. On the next visit to `app.twenty.com/authorize?…`, `MinimalMetadataGater` sees `hasAccessTokenPair === true` and renders `` instead of ``. The loader never goes away because: - `IsMinimalMetadataReadyEffect` waits for `metadataStore.status === 'up-to-date'`. - `MinimalMetadataLoadEffect` only loads metadata when `hasAccessTokenPair && isActiveWorkspace`, and the default domain has no workspace context. Skeleton loader stays forever → user perceives "blank page". Some users get rescued by `WorkspaceProviderEffect` auto-redirecting them to their last-authenticated workspace subdomain, but that cookie is set with `domain: .twenty.com` and silently fails to persist for users on custom domains — so the bug is most visible there. ### Fix `/authorize` only issues `findApplicationRegistrationByClientId`, which is a `PublicEndpointGuard` query. It doesn't need workspace metadata. Add it to the gater's excluded-paths list alongside the existing pre-auth pages (`SignInUp`, `Verify`, `Invite`, …) so the page renders immediately regardless of token state. This is a one-line, minimum-blast-radius fix. Two related cleanups are separate concerns and not addressed here: - Clearing the workspace-agnostic `tokenPair` cookie on `app.twenty.com` after workspace selection. - Fixing `lastAuthenticatedWorkspaceDomain` propagation for custom-domain users. ## Test plan - [x] `npx oxlint` + `prettier --check` on the touched file — clean. - [x] `npx nx typecheck twenty-front` — clean. - [ ] Manual: with a stale `tokenPair` cookie set on `app.twenty.com`, open `https://app.twenty.com/authorize?client_id=&…` — consent screen renders, no blank. - [ ] Manual: signed-out → open the same URL — still redirects to `/welcome` and back through the flow. - [ ] Manual: signed-in on a workspace subdomain → open `https://app.twenty.com/authorize?…` — `WorkspaceProviderEffect` auto-redirect still kicks in (unchanged behavior). --- .../modules/metadata-store/components/MinimalMetadataGater.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/twenty-front/src/modules/metadata-store/components/MinimalMetadataGater.tsx b/packages/twenty-front/src/modules/metadata-store/components/MinimalMetadataGater.tsx index 41e362d6bbc..c570d1942e6 100644 --- a/packages/twenty-front/src/modules/metadata-store/components/MinimalMetadataGater.tsx +++ b/packages/twenty-front/src/modules/metadata-store/components/MinimalMetadataGater.tsx @@ -23,7 +23,8 @@ export const MinimalMetadataGater = ({ children }: React.PropsWithChildren) => { isMatchingLocation(location, AppPath.ResetPassword) || isMatchingLocation(location, AppPath.CreateWorkspace) || isMatchingLocation(location, AppPath.PlanRequired) || - isMatchingLocation(location, AppPath.PlanRequiredSuccess); + isMatchingLocation(location, AppPath.PlanRequiredSuccess) || + isMatchingLocation(location, AppPath.Authorize); const shouldShowLoader = !isMinimalMetadataReady && !isOnExcludedPath;