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.
This commit is contained in:
kishore
2026-05-11 22:26:11 -07:00
parent 5935a1d121
commit f318596ffa
6 changed files with 52 additions and 18 deletions

View File

@@ -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": {

View File

@@ -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 () => {

View File

@@ -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()

View File

@@ -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')
}
}

View File

@@ -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

View File

@@ -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