From f318596ffa442edf96441bceb59955d834a1bf66 Mon Sep 17 00:00:00 2001 From: kishore Date: Mon, 11 May 2026 22:26:11 -0700 Subject: [PATCH] fix: surface OAuth resume errors on social-login path + i18n + test cleanup CodeRabbit follow-ups: - Surface OAuth resume failures via a toast in CloudLoginView and CloudSignupView. authError only renders in email-form mode, so a Google/GitHub user who hits a session-cookie failure would see nothing. - Move the session-error fallback string to i18n (oauth.consent.sessionError) instead of hardcoding it in the composable. - Wrap the location override in submitOAuthConsentDecision's test in try/finally so the Proxy'd globalThis.location can't leak into later tests on assertion failure. --- src/locales/en/main.json | 4 ++- src/platform/cloud/oauth/oauthApi.test.ts | 32 +++++++++++-------- .../oauth/useOAuthPostLoginRedirect.test.ts | 9 ++++++ .../cloud/oauth/useOAuthPostLoginRedirect.ts | 9 ++++-- .../cloud/onboarding/CloudLoginView.vue | 8 +++++ .../cloud/onboarding/CloudSignupView.vue | 8 +++++ 6 files changed, 52 insertions(+), 18 deletions(-) diff --git a/src/locales/en/main.json b/src/locales/en/main.json index b46bcdd56c..b65fec15a0 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -2149,7 +2149,9 @@ "appTypeWeb": "Web app", "errorExpired": "This consent request has expired or has already been used. Please restart from the client app.", "errorScopeBroadening": "The previously approved permissions don't cover this request. You'll need to re-authorize with the new permissions.", - "errorUnavailable": "This feature isn't available right now. Please contact support if the problem persists." + "errorUnavailable": "This feature isn't available right now. Please contact support if the problem persists.", + "sessionError": "Failed to establish session. Please try again.", + "sessionErrorToastSummary": "Couldn't continue OAuth sign-in" }, "scopes": { "mcp:tools:read": { diff --git a/src/platform/cloud/oauth/oauthApi.test.ts b/src/platform/cloud/oauth/oauthApi.test.ts index 93d1acd40a..29c12f2329 100644 --- a/src/platform/cloud/oauth/oauthApi.test.ts +++ b/src/platform/cloud/oauth/oauthApi.test.ts @@ -171,21 +171,25 @@ describe('submitOAuthConsentDecision', () => { }) }) - await submitOAuthConsentDecision({ - oauthRequestId: validChallenge.oauth_request_id, - csrfToken: validChallenge.csrf_token, - decision: 'allow', - workspaceId: 'personal-workspace' - }) + try { + await submitOAuthConsentDecision({ + oauthRequestId: validChallenge.oauth_request_id, + csrfToken: validChallenge.csrf_token, + decision: 'allow', + workspaceId: 'personal-workspace' + }) - expect(hrefSetter).toHaveBeenCalledWith( - 'http://127.0.0.1:50632/cb?code=xyz' - ) - - Object.defineProperty(globalThis, 'location', { - configurable: true, - value: originalLocation - }) + expect(hrefSetter).toHaveBeenCalledWith( + 'http://127.0.0.1:50632/cb?code=xyz' + ) + } finally { + // Restore unconditionally so an assertion failure doesn't leak the + // Proxy'd location into later tests. + Object.defineProperty(globalThis, 'location', { + configurable: true, + value: originalLocation + }) + } }) it('throws OAuthApiError on non-2xx', async () => { diff --git a/src/platform/cloud/oauth/useOAuthPostLoginRedirect.test.ts b/src/platform/cloud/oauth/useOAuthPostLoginRedirect.test.ts index 1935223b78..df85bb91bb 100644 --- a/src/platform/cloud/oauth/useOAuthPostLoginRedirect.test.ts +++ b/src/platform/cloud/oauth/useOAuthPostLoginRedirect.test.ts @@ -15,6 +15,15 @@ vi.mock('@/platform/auth/session/useSessionCookie', () => ({ useSessionCookie: () => ({ createSessionOrThrow }) })) +vi.mock('vue-i18n', () => ({ + useI18n: () => ({ + t: (key: string) => + key === 'oauth.consent.sessionError' + ? 'Failed to establish session. Please try again.' + : key + }) +})) + describe('useOAuthPostLoginRedirect', () => { beforeEach(() => { sessionStorage.clear() diff --git a/src/platform/cloud/oauth/useOAuthPostLoginRedirect.ts b/src/platform/cloud/oauth/useOAuthPostLoginRedirect.ts index 65ba2cdca5..2371e0c38d 100644 --- a/src/platform/cloud/oauth/useOAuthPostLoginRedirect.ts +++ b/src/platform/cloud/oauth/useOAuthPostLoginRedirect.ts @@ -1,3 +1,4 @@ +import { useI18n } from 'vue-i18n' import type { LocationQuery } from 'vue-router' import { useRouter } from 'vue-router' @@ -12,8 +13,6 @@ type OAuthResumeResult = | { kind: 'resumed' } | { kind: 'error'; message: string } -const FALLBACK_ERROR_MESSAGE = 'Failed to establish session. Please try again.' - /** * Post-login OAuth resume. If the current login flow originated from an OAuth * authorize request, establishes the Cloud session cookie and navigates to the @@ -22,6 +21,7 @@ const FALLBACK_ERROR_MESSAGE = 'Failed to establish session. Please try again.' export function useOAuthPostLoginRedirect() { const router = useRouter() const sessionCookie = useSessionCookie() + const { t } = useI18n() async function resumeOAuthIfNeeded( query: LocationQuery @@ -35,7 +35,10 @@ export function useOAuthPostLoginRedirect() { } catch (error) { return { kind: 'error', - message: error instanceof Error ? error.message : FALLBACK_ERROR_MESSAGE + message: + error instanceof Error + ? error.message + : t('oauth.consent.sessionError') } } diff --git a/src/platform/cloud/onboarding/CloudLoginView.vue b/src/platform/cloud/onboarding/CloudLoginView.vue index e369acd0f6..a5b7f72f9f 100644 --- a/src/platform/cloud/onboarding/CloudLoginView.vue +++ b/src/platform/cloud/onboarding/CloudLoginView.vue @@ -157,7 +157,15 @@ const onSuccess = async () => { const oauthResume = await resumeOAuthIfNeeded(route.query) if (oauthResume.kind === 'error') { + // authError renders only in email-form mode; surface the failure via + // a toast so social-login users (Google / GitHub) can see it too. authError.value = oauthResume.message + toastStore.add({ + severity: 'error', + summary: t('oauth.consent.sessionErrorToastSummary'), + detail: oauthResume.message, + life: 4000 + }) return } if (oauthResume.kind === 'resumed') return diff --git a/src/platform/cloud/onboarding/CloudSignupView.vue b/src/platform/cloud/onboarding/CloudSignupView.vue index 3daf1b1168..2a0dd1a01f 100644 --- a/src/platform/cloud/onboarding/CloudSignupView.vue +++ b/src/platform/cloud/onboarding/CloudSignupView.vue @@ -183,7 +183,15 @@ const onSuccess = async () => { const oauthResume = await resumeOAuthIfNeeded(route.query) if (oauthResume.kind === 'error') { + // authError renders only in email-form mode; surface the failure via + // a toast so social-login users (Google / GitHub) can see it too. authError.value = oauthResume.message + toastStore.add({ + severity: 'error', + summary: t('oauth.consent.sessionErrorToastSummary'), + detail: oauthResume.message, + life: 4000 + }) return } if (oauthResume.kind === 'resumed') return