拆分 OAuth 后置验证流程

This commit is contained in:
QLHazyCoder
2026-05-15 23:03:02 +08:00
parent 4c2ab31b07
commit 3f3f9275cc
26 changed files with 1578 additions and 584 deletions
+278 -140
View File
@@ -2,8 +2,6 @@
root.MultiPageBackgroundStep8 = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep8Module() {
const MAIL_2925_FILTER_LOOKBACK_MS = 10 * 60 * 1000;
const STEP8_ADD_EMAIL_URL = 'https://auth.openai.com/add-email';
const STEP8_CURRENT_STEP_RECOVERY_MAX_ATTEMPTS = 3;
function createStep8Executor(deps = {}) {
const {
@@ -30,7 +28,6 @@
rerunStep7ForStep8Recovery,
reuseOrCreateTab,
sendToContentScriptResilient,
buildRegistrationEmailStateUpdates = null,
persistRegistrationEmailState = null,
phoneVerificationHelpers = null,
setState,
@@ -41,6 +38,7 @@
throwIfStopped,
} = deps;
let activeFetchLoginCodeStep = null;
let activeFetchLoginCodeStepKey = 'fetch-login-code';
function normalizeLogStep(value) {
const step = Math.floor(Number(value) || 0);
@@ -61,7 +59,7 @@
if (step) {
normalizedOptions.step = step;
if (!normalizedOptions.stepKey) {
normalizedOptions.stepKey = 'fetch-login-code';
normalizedOptions.stepKey = activeFetchLoginCodeStepKey || 'fetch-login-code';
}
}
delete normalizedOptions.visibleStep;
@@ -73,6 +71,27 @@
return visibleStep > 0 ? visibleStep : fallback;
}
function normalizeSignupMethod(value = '') {
return String(value || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email';
}
function normalizeIdentifierType(value = '') {
const normalized = String(value || '').trim().toLowerCase();
return normalized === 'phone' || normalized === 'email' ? normalized : '';
}
function isPhoneLoginCodeMode(state = {}) {
if (normalizeIdentifierType(state?.accountIdentifierType) === 'phone') {
return true;
}
return normalizeSignupMethod(state?.resolvedSignupMethod || state?.signupMethod) === 'phone'
&& Boolean(
String(state?.signupPhoneNumber || '').trim()
|| String(state?.signupPhoneCompletedActivation?.phoneNumber || '').trim()
|| String(state?.signupPhoneActivation?.phoneNumber || '').trim()
);
}
function getAuthLoginStepForVisibleStep(visibleStep) {
return visibleStep >= 11 ? 10 : 7;
}
@@ -173,7 +192,7 @@
retryDelayMs: 700,
logMessage: `步骤 ${visibleStep}:添加邮箱页面正在切换,等待邮箱验证码页就绪...`,
logStep: visibleStep,
logStepKey: 'fetch-login-code',
logStepKey: activeFetchLoginCodeStepKey || 'fetch-login-code',
}
);
@@ -185,7 +204,7 @@
let persistedState = latestState;
if (typeof persistRegistrationEmailState === 'function') {
await persistRegistrationEmailState(latestState, resolvedEmail, {
source: 'step8_add_email',
source: activeFetchLoginCodeStepKey === 'bind-email' ? 'bind_email' : 'step8_add_email',
preserveAccountIdentity: true,
});
persistedState = typeof getState === 'function' ? await getState() : latestState;
@@ -234,73 +253,48 @@
}
}
async function completeStep8WhenDeferredToPostLoginPhone(visibleStep, pageState = {}, options = {}) {
await setState({
step8VerificationTargetEmail: '',
loginVerificationRequestedAt: null,
});
await addLog(
`步骤 ${visibleStep}:当前认证页已进入手机号验证流程,跳过登录邮箱验证码,交给后续“手机号验证”步骤处理。`,
'warn'
);
if (typeof completeNodeFromBackground === 'function') {
await completeNodeFromBackground(options.nodeId || 'fetch-login-code', {
loginVerificationRequestedAt: null,
skipLoginVerificationStep: true,
addPhonePage: pageState?.state === 'add_phone_page' || Boolean(pageState?.addPhonePage),
phoneVerificationPage: pageState?.state === 'phone_verification_page' || Boolean(pageState?.phoneVerificationPage),
});
}
}
async function completeStep8WhenDeferredToBindEmail(visibleStep, options = {}) {
await setState({
step8VerificationTargetEmail: '',
loginVerificationRequestedAt: null,
});
await addLog(
`步骤 ${visibleStep}:当前认证页已进入添加邮箱页,跳过登录短信验证码,交给后续“绑定邮箱”步骤处理。`,
'warn'
);
if (typeof completeNodeFromBackground === 'function') {
await completeNodeFromBackground(options.nodeId || 'fetch-login-code', {
loginVerificationRequestedAt: null,
skipLoginVerificationStep: true,
addEmailPage: true,
});
}
}
function isStep8AddPhoneStateError(error) {
const message = String(error?.message || error || '');
return /add-phone|手机号页面|手机号验证页|phone[\s-_]verification|phone\s+number/i.test(message);
}
function isStep8EmailInUseError(error) {
const message = String(error?.message || error || '');
return /STEP8_EMAIL_IN_USE::|email_in_use|email\s+(?:address\s+)?already\s+exists|already\s+associated\s+with\s+this\s+email/i.test(message);
}
function isStep8MaxCheckAttemptsError(error) {
const message = String(error?.message || error || '');
return /AUTH_MAX_CHECK_ATTEMPTS::|max_check_attempts/i.test(message);
}
async function openStep8AddEmailPage(state, visibleStep, reasonLabel = '') {
const tabId = typeof getTabId === 'function' ? await getTabId('signup-page') : 0;
const url = STEP8_ADD_EMAIL_URL;
if (tabId && chrome?.tabs?.update) {
await chrome.tabs.update(tabId, { url, active: true });
} else if (typeof reuseOrCreateTab === 'function') {
await reuseOrCreateTab('signup-page', url);
} else {
throw new Error(`Step ${visibleStep}: cannot reopen add-email page for Step 8 recovery.`);
}
if (typeof sleepWithStop === 'function') {
await sleepWithStop(1000);
}
await addLog(
`步骤 ${visibleStep}:重新打开添加邮箱页面${reasonLabel ? `${reasonLabel}` : ''}`,
'warn'
);
return {
...(state || {}),
oauthUrl: state?.oauthUrl || url,
};
}
async function resetStep8AfterEmailInUse(state, visibleStep) {
const currentEmail = String(state?.email || '').trim();
const registrationEmailUpdates = typeof buildRegistrationEmailStateUpdates === 'function'
? buildRegistrationEmailStateUpdates(state, {
currentEmail: null,
preservePrevious: true,
source: 'step8_recovery',
})
: { email: null };
await setState({
...registrationEmailUpdates,
step8VerificationTargetEmail: '',
loginVerificationRequestedAt: null,
});
if (currentEmail) {
await addLog(`步骤 ${visibleStep}:检测到邮箱 ${currentEmail} 已被占用,已清理运行态并准备重新获取新邮箱。`, 'warn');
} else {
await addLog(`步骤 ${visibleStep}:检测到邮箱已被占用,已清理运行态并准备重新获取新邮箱。`, 'warn');
}
}
async function resetStep8AfterMaxCheckAttempts(visibleStep) {
await setState({
step8VerificationTargetEmail: '',
loginVerificationRequestedAt: null,
});
await addLog(`步骤 ${visibleStep}:检测到 max_check_attempts,将重新开始当前添加邮箱步骤,不继续点击重试。`, 'warn');
}
async function recoverStep8PollingFailure(currentState, visibleStep) {
const authLoginStep = getAuthLoginStepForVisibleStep(visibleStep);
try {
@@ -409,79 +403,142 @@
return result || {};
}
async function runStep8Attempt(state, runtime = {}) {
const visibleStep = getVisibleStep(state, 8);
activeFetchLoginCodeStep = visibleStep;
async function ensureAuthTabForPostLoginStep(state, visibleStep) {
const authTabId = await getTabId('signup-page');
if (authTabId) {
await chrome.tabs.update(authTabId, { active: true });
} else {
if (!state.oauthUrl) {
throw new Error(`缺少登录用 OAuth 链接,请先完成步骤 ${getAuthLoginStepForVisibleStep(visibleStep)}`);
}
await reuseOrCreateTab('signup-page', state.oauthUrl);
return authTabId;
}
if (!state?.oauthUrl) {
throw new Error(`步骤 ${visibleStep}:缺少登录用 OAuth 链接,请先完成刷新 OAuth 并登录。`);
}
return reuseOrCreateTab('signup-page', state.oauthUrl);
}
async function completePostLoginPhoneVerificationSkippedOnOauth(visibleStep, options = {}) {
await addLog(`步骤 ${visibleStep}:当前认证页已进入 OAuth 授权页,跳过手机号验证步骤。`, 'warn', {
step: visibleStep,
stepKey: 'post-login-phone-verification',
});
if (typeof completeNodeFromBackground === 'function') {
await completeNodeFromBackground(options.nodeId || 'post-login-phone-verification', {
directOAuthConsentPage: true,
phoneVerification: false,
});
}
}
async function executePostLoginPhoneVerification(state) {
const visibleStep = getVisibleStep(state, 9);
activeFetchLoginCodeStep = visibleStep;
activeFetchLoginCodeStepKey = 'post-login-phone-verification';
const authTabId = await ensureAuthTabForPostLoginStep(state, visibleStep);
const pageState = await getLoginAuthStateFromContent(visibleStep, {
timeoutMs: await getStep8ReadyTimeoutMs('确认手机号验证页或 OAuth 授权页已就绪', state?.oauthUrl || '', visibleStep),
logMessage: `步骤 ${visibleStep}:正在确认是否需要手机号验证...`,
});
if (pageState?.state === 'oauth_consent_page') {
await completePostLoginPhoneVerificationSkippedOnOauth(visibleStep, { nodeId: state?.nodeId });
return;
}
if (pageState?.state !== 'add_phone_page' && pageState?.state !== 'phone_verification_page') {
throw new Error(`步骤 ${visibleStep}:手机号验证步骤只处理添加手机号页或手机验证码页,当前状态:${pageState?.state || 'unknown'}。URL: ${pageState?.url || ''}`.trim());
}
if (!state?.phoneVerificationEnabled) {
throw new Error(`步骤 ${visibleStep}:检测到需要手机号验证,但手机接码未开启。URL: ${pageState?.url || ''}`.trim());
}
if (typeof phoneVerificationHelpers?.completePhoneVerificationFlow !== 'function') {
throw new Error(`步骤 ${visibleStep}:手机号验证流程不可用,接码模块尚未初始化。`);
}
const stateLastResendAt = Number(state?.loginVerificationRequestedAt) || 0;
let latestResendAt = Math.max(0, Number(runtime?.stickyLastResendAt) || 0, stateLastResendAt);
const result = await phoneVerificationHelpers.completePhoneVerificationFlow(authTabId, pageState, {
step: visibleStep,
visibleStep,
});
if (typeof completeNodeFromBackground === 'function') {
await completeNodeFromBackground(state?.nodeId || 'post-login-phone-verification', {
phoneVerification: true,
postLoginPhoneVerification: true,
code: result?.code || '',
});
}
return result || {};
}
async function executeBindEmail(state) {
const visibleStep = getVisibleStep(state, 9);
activeFetchLoginCodeStep = visibleStep;
activeFetchLoginCodeStepKey = 'bind-email';
await ensureAuthTabForPostLoginStep(state, visibleStep);
const pageState = await getLoginAuthStateFromContent(visibleStep, {
timeoutMs: await getStep8ReadyTimeoutMs('确认添加邮箱页或 OAuth 授权页已就绪', state?.oauthUrl || '', visibleStep),
logMessage: `步骤 ${visibleStep}:正在确认是否需要绑定邮箱...`,
});
if (pageState?.state === 'oauth_consent_page') {
await addLog(`步骤 ${visibleStep}:当前认证页已进入 OAuth 授权页,跳过绑定邮箱步骤。`, 'warn', {
step: visibleStep,
stepKey: 'bind-email',
});
if (typeof completeNodeFromBackground === 'function') {
await completeNodeFromBackground(state?.nodeId || 'bind-email', {
directOAuthConsentPage: true,
bindEmailSubmitted: false,
});
}
return;
}
if (pageState?.state !== 'add_email_page') {
throw new Error(`步骤 ${visibleStep}:绑定邮箱步骤只处理添加邮箱页,当前状态:${pageState?.state || 'unknown'}。URL: ${pageState?.url || ''}`.trim());
}
const addEmailPreparation = await submitAddEmailIfNeeded(state, visibleStep, pageState);
const preparedState = addEmailPreparation?.state || state;
const nextPageState = addEmailPreparation?.pageState || pageState;
if (nextPageState?.state !== 'verification_page') {
throw new Error(`步骤 ${visibleStep}:绑定邮箱提交后必须进入邮箱验证码页,当前状态:${nextPageState?.state || 'unknown'}。URL: ${nextPageState?.url || ''}`.trim());
}
if (typeof completeNodeFromBackground === 'function') {
await completeNodeFromBackground(state?.nodeId || 'bind-email', {
bindEmailSubmitted: true,
email: preparedState?.email || '',
step8VerificationTargetEmail: preparedState?.step8VerificationTargetEmail || nextPageState?.displayedEmail || '',
});
}
}
async function pollEmailVerificationCode(preparedState, pageState, visibleStep, runtime = {}) {
let latestResendAt = Math.max(
0,
Number(runtime?.stickyLastResendAt) || 0,
Number(preparedState?.loginVerificationRequestedAt) || 0
);
const notifyResendRequestedAt = typeof runtime?.onResendRequestedAt === 'function'
? runtime.onResendRequestedAt
: null;
throwIfStopped();
let pageState = await ensureStep8VerificationPageReady({
visibleStep,
authLoginStep: getAuthLoginStepForVisibleStep(visibleStep),
allowPhoneVerificationPage: true,
allowAddEmailPage: true,
timeoutMs: await getStep8ReadyTimeoutMs('确认登录验证码页已就绪', state?.oauthUrl || '', visibleStep),
});
if (pageState?.state === 'oauth_consent_page') {
await completeStep8WhenAuthAlreadyOnOauthConsent(visibleStep, { nodeId: state?.nodeId });
return;
}
if (pageState?.state === 'phone_verification_page') {
return executeLoginPhoneCodeStep(state, authTabId, visibleStep);
}
let preparedState = state;
const addEmailPreparation = await submitAddEmailIfNeeded(preparedState, visibleStep, pageState);
preparedState = addEmailPreparation?.state || preparedState;
pageState = addEmailPreparation?.pageState || pageState;
if (pageState?.state === 'oauth_consent_page') {
await completeStep8WhenAuthAlreadyOnOauthConsent(visibleStep, { nodeId: preparedState?.nodeId || state?.nodeId });
return;
}
if (pageState?.state === 'phone_verification_page') {
return executeLoginPhoneCodeStep(preparedState, authTabId, visibleStep);
}
const preparedStateLastResendAt = Number(preparedState?.loginVerificationRequestedAt) || 0;
if (preparedStateLastResendAt > 0) {
latestResendAt = Math.max(latestResendAt, preparedStateLastResendAt);
}
const mail = getMailConfig(preparedState);
if (mail.error) throw new Error(mail.error);
const stepStartedAt = Date.now();
const verificationFilterAfterTimestamp = mail.provider === '2925'
? Math.max(0, stepStartedAt - MAIL_2925_FILTER_LOOKBACK_MS)
: stepStartedAt;
const verificationSessionKey = `8:${stepStartedAt}`;
const verificationSessionKey = `${visibleStep}:${stepStartedAt}`;
const shouldCompareVerificationEmail = mail.provider !== '2925';
const displayedVerificationEmail = shouldCompareVerificationEmail
? normalizeStep8VerificationTargetEmail(pageState?.displayedEmail)
: '';
const fixedTargetEmail = shouldCompareVerificationEmail
? (displayedVerificationEmail || normalizeStep8VerificationTargetEmail(preparedState?.email))
? (displayedVerificationEmail || normalizeStep8VerificationTargetEmail(preparedState?.step8VerificationTargetEmail || preparedState?.email))
: '';
await setState({
step8VerificationTargetEmail: displayedVerificationEmail || '',
});
await addLog(`步骤 ${visibleStep}登录验证码页面已就绪,开始获取验证码。`, 'info');
await addLog(`步骤 ${visibleStep}邮箱验证码页面已就绪,开始获取验证码。`, 'info');
if (shouldCompareVerificationEmail && displayedVerificationEmail) {
await addLog(`步骤 ${visibleStep}:已固定当前验证码页显示邮箱 ${displayedVerificationEmail} 作为后续匹配目标。`, 'info');
}
@@ -491,7 +548,7 @@
completionStep: visibleStep,
promptStep: visibleStep,
});
return;
return { lastResendAt: latestResendAt };
}
if (mail.source === 'icloud-mail' && typeof ensureIcloudMailSession === 'function') {
@@ -564,6 +621,104 @@
};
}
async function completeFetchBindEmailCodeSkippedOnOauth(visibleStep, options = {}) {
await addLog(`步骤 ${visibleStep}:当前认证页已进入 OAuth 授权页,跳过绑定邮箱验证码步骤。`, 'warn', {
step: visibleStep,
stepKey: 'fetch-bind-email-code',
});
if (typeof completeNodeFromBackground === 'function') {
await completeNodeFromBackground(options.nodeId || 'fetch-bind-email-code', {
directOAuthConsentPage: true,
bindEmailCodeSkipped: true,
});
}
}
async function executeFetchBindEmailCode(state) {
const visibleStep = getVisibleStep(state, 10);
activeFetchLoginCodeStep = visibleStep;
activeFetchLoginCodeStepKey = 'fetch-bind-email-code';
await ensureAuthTabForPostLoginStep(state, visibleStep);
const pageState = await getLoginAuthStateFromContent(visibleStep, {
timeoutMs: await getStep8ReadyTimeoutMs('确认绑定邮箱验证码页已就绪', state?.oauthUrl || '', visibleStep),
logMessage: `步骤 ${visibleStep}:正在确认绑定邮箱验证码页...`,
});
if (pageState?.state === 'oauth_consent_page') {
if (state?.bindEmailSubmitted) {
throw new Error(`步骤 ${visibleStep}:绑定邮箱提交后不应直接进入 OAuth 授权页,必须先完成邮箱验证码。URL: ${pageState?.url || ''}`.trim());
}
await completeFetchBindEmailCodeSkippedOnOauth(visibleStep, { nodeId: state?.nodeId });
return;
}
if (pageState?.state !== 'verification_page') {
throw new Error(`步骤 ${visibleStep}:获取绑定邮箱验证码步骤只处理邮箱验证码页,当前状态:${pageState?.state || 'unknown'}。URL: ${pageState?.url || ''}`.trim());
}
if (!state?.bindEmailSubmitted) {
throw new Error(`步骤 ${visibleStep}:尚未完成绑定邮箱提交,不能直接获取绑定邮箱验证码。`);
}
return pollEmailVerificationCode(state, pageState, visibleStep, {
stickyLastResendAt: Number(state?.loginVerificationRequestedAt) || 0,
});
}
async function runStep8Attempt(state, runtime = {}) {
const visibleStep = getVisibleStep(state, 8);
activeFetchLoginCodeStep = visibleStep;
activeFetchLoginCodeStepKey = 'fetch-login-code';
const authTabId = await getTabId('signup-page');
if (authTabId) {
await chrome.tabs.update(authTabId, { active: true });
} else {
if (!state.oauthUrl) {
throw new Error(`缺少登录用 OAuth 链接,请先完成步骤 ${getAuthLoginStepForVisibleStep(visibleStep)}`);
}
await reuseOrCreateTab('signup-page', state.oauthUrl);
}
throwIfStopped();
let pageState = await ensureStep8VerificationPageReady({
visibleStep,
authLoginStep: getAuthLoginStepForVisibleStep(visibleStep),
allowPhoneVerificationPage: true,
allowAddEmailPage: true,
timeoutMs: await getStep8ReadyTimeoutMs('确认登录验证码页已就绪', state?.oauthUrl || '', visibleStep),
});
if (pageState?.state === 'oauth_consent_page') {
await completeStep8WhenAuthAlreadyOnOauthConsent(visibleStep, { nodeId: state?.nodeId });
return;
}
const phoneLoginCodeMode = isPhoneLoginCodeMode(state);
if (phoneLoginCodeMode) {
if (pageState?.state === 'phone_verification_page') {
return executeLoginPhoneCodeStep(state, authTabId, visibleStep);
}
if (pageState?.state === 'add_email_page') {
await completeStep8WhenDeferredToBindEmail(visibleStep, { nodeId: state?.nodeId });
return;
}
if (pageState?.state === 'verification_page') {
throw new Error(`步骤 ${visibleStep}:手机号注册模式只允许处理手机登录验证码,当前进入了普通邮箱登录验证码页,不会回落到邮箱 provider。URL: ${pageState?.url || ''}`.trim());
}
if (pageState?.state === 'add_phone_page') {
throw new Error(`步骤 ${visibleStep}:手机号注册模式不应进入添加手机号页。URL: ${pageState?.url || ''}`.trim());
}
throw new Error(`步骤 ${visibleStep}:手机号注册模式登录验证码步骤进入了不允许的页面:${pageState?.state || 'unknown'}。URL: ${pageState?.url || ''}`.trim());
}
if (pageState?.state === 'add_phone_page' || pageState?.state === 'phone_verification_page') {
await completeStep8WhenDeferredToPostLoginPhone(visibleStep, pageState, { nodeId: state?.nodeId });
return;
}
if (pageState?.state === 'add_email_page') {
throw new Error(`步骤 ${visibleStep}:邮箱注册模式不应进入添加邮箱页。URL: ${pageState?.url || ''}`.trim());
}
return pollEmailVerificationCode(state, pageState, visibleStep, runtime);
}
function isStep8RestartStep7Error(error) {
const message = String(error?.message || error || '');
return /STEP8_RESTART_STEP7::/i.test(message);
@@ -576,7 +731,6 @@
let stickyLastResendAt = Number(state?.loginVerificationRequestedAt) || 0;
let retryWithoutStep7Streak = 0;
const maxRetryWithoutStep7Streak = 3;
let currentNodeRecoveryAttempt = 0;
while (true) {
try {
@@ -600,27 +754,6 @@
let currentError = err;
let retryWithoutStep7 = false;
if (isStep8EmailInUseError(currentError) || isStep8MaxCheckAttemptsError(currentError)) {
currentNodeRecoveryAttempt += 1;
if (currentNodeRecoveryAttempt > STEP8_CURRENT_STEP_RECOVERY_MAX_ATTEMPTS) {
throw currentError;
}
if (isStep8EmailInUseError(currentError)) {
await resetStep8AfterEmailInUse(currentState, visibleStep);
await openStep8AddEmailPage(currentState, visibleStep, 'email_in_use');
} else {
await resetStep8AfterMaxCheckAttempts(visibleStep);
await openStep8AddEmailPage(currentState, visibleStep, 'max_check_attempts');
}
const latestState = typeof getState === 'function' ? await getState() : currentState;
currentState = {
...(currentState || {}),
...(latestState || {}),
oauthUrl: currentState?.oauthUrl || latestState?.oauthUrl || STEP8_ADD_EMAIL_URL,
};
continue;
}
const isMailPollingError = isVerificationMailPollingError(err);
if (isMailPollingError && !isStep8RestartStep7Error(err)) {
const recovery = await recoverStep8PollingFailure(currentState, visibleStep);
@@ -717,7 +850,12 @@
throw new Error(`步骤 ${visibleStep}:登录验证码流程未成功完成。`);
}
return { executeStep8 };
return {
executeStep8,
executePostLoginPhoneVerification,
executeBindEmail,
executeFetchBindEmailCode,
};
}
return { createStep8Executor };