diff --git a/flows/openai/content/openai-auth.js b/flows/openai/content/openai-auth.js index 0defb8a..8479387 100644 --- a/flows/openai/content/openai-auth.js +++ b/flows/openai/content/openai-auth.js @@ -5767,6 +5767,40 @@ async function waitForLoginEntryOpenTransition(timeout = 10000) { return snapshot; } +function isOpenAiOAuthAuthorizationRoute(rawUrl = '') { + const value = String(rawUrl || '').trim(); + if (!value) { + return false; + } + + try { + const parsed = new URL(value, location.href); + const hostname = String(parsed.hostname || '').toLowerCase(); + if (!['auth.openai.com', 'auth0.openai.com', 'accounts.openai.com'].includes(hostname)) { + return false; + } + + const pathname = parsed.pathname || '/'; + return /^\/(?:authorize|oauth(?:2)?\/|api\/oauth\/oauth2\/auth|sign-in-with-chatgpt\/)/i.test(pathname); + } catch { + return false; + } +} + +function isPostChooseAccountOAuthRoute(snapshot = {}) { + const url = String(snapshot?.url || location.href || '').trim(); + if (!isOpenAiOAuthAuthorizationRoute(url)) { + return false; + } + + try { + const parsed = new URL(url, location.href); + return !/\/choose-an-account(?:[/?#]|$)/i.test(parsed.pathname || ''); + } catch { + return !/\/choose-an-account(?:[/?#]|$)/i.test(url); + } +} + async function waitForChooseAccountTransition(timeout = 15000) { const start = Date.now(); let snapshot = normalizeStep6Snapshot(inspectLoginAuthState()); @@ -5774,6 +5808,12 @@ async function waitForChooseAccountTransition(timeout = 15000) { while (Date.now() - start < timeout) { throwIfStopped(); snapshot = normalizeStep6Snapshot(inspectLoginAuthState()); + if (isPostChooseAccountOAuthRoute(snapshot)) { + return { + ...snapshot, + oauthAuthorizationRoute: true, + }; + } if (snapshot.state !== 'unknown' && snapshot.state !== 'choose_account_page') { return snapshot; } @@ -5826,6 +5866,11 @@ async function step6ChooseExistingAccount(payload, snapshot) { via: 'choose_account_oauth_consent_page', }); } + if (nextSnapshot.oauthAuthorizationRoute || isPostChooseAccountOAuthRoute(nextSnapshot)) { + return createStep6OAuthConsentSuccessResult(nextSnapshot, { + via: 'choose_account_oauth_authorization_route', + }); + } if (nextSnapshot.state === 'add_email_page') { return createStep6AddEmailSuccessResult(nextSnapshot, { via: 'choose_account_add_email_page', diff --git a/tests/step6-oauth-consent-skip.test.js b/tests/step6-oauth-consent-skip.test.js index 28c1873..5f756b3 100644 --- a/tests/step6-oauth-consent-skip.test.js +++ b/tests/step6-oauth-consent-skip.test.js @@ -253,6 +253,8 @@ ${extractFunction('createStep6OAuthConsentSuccessResult')} ${extractFunction('createStep6AddEmailSuccessResult')} ${extractFunction('createStep6RecoverableResult')} ${extractFunction('normalizeStep6Snapshot')} +${extractFunction('isOpenAiOAuthAuthorizationRoute')} +${extractFunction('isPostChooseAccountOAuthRoute')} ${extractFunction('waitForChooseAccountTransition')} ${extractFunction('step6ChooseExistingAccount')} @@ -277,6 +279,113 @@ return { assert.equal(result.via, 'choose_account_oauth_consent_page'); }); +test('step 7 skips login code when choose-account leaves for OAuth authorize route before consent DOM is ready', async () => { + const api = new Function(` +let pageState = 'choose_account_page'; +const clicked = []; +const location = { + href: 'https://auth.openai.com/choose-an-account', + pathname: '/choose-an-account', +}; +const targetCard = { + id: 'target-card', + textContent: 'Tall Slept Fancy tall-slept-fancy@duck.com', + value: '', + parentElement: null, + disabled: false, + getAttribute(name) { + if (name === 'role') return 'button'; + if (name === 'aria-disabled') return 'false'; + return ''; + }, + closest() { + return null; + }, +}; + +const document = { + body: { + innerText: 'Welcome back Choose an account tall-slept-fancy@duck.com', + textContent: 'Welcome back Choose an account tall-slept-fancy@duck.com', + }, + querySelectorAll(selector) { + if (String(selector).includes('body *')) return [targetCard]; + return [targetCard]; + }, +}; + +function getOperationDelayRunner() { + return async (_metadata, operation) => operation(); +} +function isVisibleElement(element) { + return Boolean(element); +} +function isActionEnabled(element) { + return Boolean(element) && !element.disabled && element.getAttribute('aria-disabled') !== 'true'; +} +function simulateClick(element) { + clicked.push(element.id); + if (element === targetCard) { + pageState = 'unknown'; + location.href = 'https://auth.openai.com/authorize?client_id=codex-test&state=oauth-state'; + location.pathname = '/authorize'; + } +} +function inspectLoginAuthState() { + return { state: pageState, url: location.href, chooseAccountPage: pageState === 'choose_account_page' }; +} +function throwIfStopped() {} +async function sleep() {} +async function humanPause() {} +function log() {} +async function finalizeStep6VerificationReady() { return { routed: 'verification' }; } +async function step6LoginFromPasswordPage() { return { routed: 'password' }; } +async function step6LoginFromEmailPage() { return { routed: 'email' }; } +async function step6LoginFromPhonePage() { return { routed: 'phone' }; } +async function createStep6LoginTimeoutRecoveryTransition() { return { action: 'recoverable', result: { routed: 'timeout' } }; } + +${extractConst('CHOOSE_ACCOUNT_PAGE_PATTERN')} +${extractConst('CHOOSE_ACCOUNT_REMOVE_ACTION_PATTERN')} +${extractConst('CHOOSE_ACCOUNT_OTHER_ACCOUNT_PATTERN')} +${extractConst('CHOOSE_ACCOUNT_ACTION_SELECTOR')} +${extractFunction('getPageTextSnapshot')} +${extractFunction('normalizeAuthAccountIdentifier')} +${extractFunction('getChooseAccountCandidateText')} +${extractFunction('isChooseAccountPage')} +${extractFunction('isChooseAccountRemovalAction')} +${extractFunction('resolveChooseAccountClickTarget')} +${extractFunction('findChooseAccountButtonForEmail')} +${extractFunction('createStep6SuccessResult')} +${extractFunction('createStep6OAuthConsentSuccessResult')} +${extractFunction('createStep6AddEmailSuccessResult')} +${extractFunction('createStep6RecoverableResult')} +${extractFunction('normalizeStep6Snapshot')} +${extractFunction('isOpenAiOAuthAuthorizationRoute')} +${extractFunction('isPostChooseAccountOAuthRoute')} +${extractFunction('waitForChooseAccountTransition')} +${extractFunction('step6ChooseExistingAccount')} + +return { + clicked, + run() { + return step6ChooseExistingAccount( + { email: 'tall-slept-fancy@duck.com', loginIdentifierType: 'email', visibleStep: 7 }, + { state: 'choose_account_page', url: location.href } + ); + }, +}; +`)(); + + const result = await api.run(); + + assert.deepEqual(api.clicked, ['target-card']); + assert.equal(result.step6Outcome, 'success'); + assert.equal(result.state, 'unknown'); + assert.equal(result.skipLoginVerificationStep, true); + assert.equal(result.directOAuthConsentPage, true); + assert.equal(result.via, 'choose_account_oauth_authorization_route'); +}); + test('step 7 does not click choose-account page when target email is missing', async () => { const api = new Function(` const clicked = []; @@ -350,6 +459,8 @@ ${extractFunction('createStep6OAuthConsentSuccessResult')} ${extractFunction('createStep6AddEmailSuccessResult')} ${extractFunction('createStep6RecoverableResult')} ${extractFunction('normalizeStep6Snapshot')} +${extractFunction('isOpenAiOAuthAuthorizationRoute')} +${extractFunction('isPostChooseAccountOAuthRoute')} ${extractFunction('waitForChooseAccountTransition')} ${extractFunction('step6ChooseExistingAccount')}