fix: restore dev flow compatibility in CPA auth hardening

This commit is contained in:
cnxwzy
2026-04-27 22:54:15 +08:00
parent f9f8df19d3
commit 936aec398e
6 changed files with 654 additions and 121 deletions
+124 -57
View File
@@ -10,6 +10,8 @@
const RUNTIME_DEFAULTS = {
contributionMode: false,
contributionModeExpected: false,
contributionSource: 'sub2api',
contributionTargetGroupName: 'codex号池',
contributionNickname: '',
contributionQq: '',
contributionSessionId: '',
@@ -38,6 +40,8 @@
} = deps;
let listenersBound = false;
const pendingCallbackSubmissions = new Map();
const pendingCapturedCallbacks = new Map();
function normalizeString(value = '') {
return String(value || '').trim();
@@ -256,6 +260,41 @@
return qq;
}
function isPlusModeState(state = {}) {
return Boolean(state?.plusModeEnabled);
}
function normalizeContributionModeSource(value = '') {
const normalized = normalizeString(value).toLowerCase();
return normalized === 'sub2api' ? 'sub2api' : 'cpa';
}
function resolveContributionModeRoutingState(state = {}) {
const currentStatus = normalizeString(state?.contributionStatus).toLowerCase();
const currentSource = normalizeContributionModeSource(state?.contributionSource);
const hasActiveSession = Boolean(
normalizeString(state?.contributionSessionId)
&& currentStatus
&& !FINAL_STATUSES.has(currentStatus)
);
if (hasActiveSession) {
return {
source: currentSource,
targetGroupName: currentSource === 'sub2api'
? (normalizeString(state?.contributionTargetGroupName) || 'codex号池')
: '',
};
}
return {
source: 'sub2api',
targetGroupName: isPlusModeState(state)
? 'openai-plus'
: (normalizeString(state?.contributionTargetGroupName) || 'codex号池'),
};
}
function buildStatusMessage(status, payload = {}) {
const label = getStatusLabel(status);
const details = [
@@ -435,46 +474,58 @@
return currentState;
}
await applyRuntimeUpdates({
contributionCallbackUrl: normalizedUrl,
contributionCallbackStatus: 'submitting',
contributionCallbackMessage: buildCallbackMessage('submitting'),
});
try {
const payload = await fetchContributionJson('/submit-callback', {
method: 'POST',
body: {
session_id: sessionId,
callback_url: normalizedUrl,
},
});
const nextStatus = 'submitted';
await applyRuntimeUpdates({
contributionCallbackUrl: normalizedUrl,
contributionCallbackStatus: nextStatus,
contributionCallbackMessage: buildCallbackMessage(nextStatus, payload),
});
if (typeof closeLocalhostCallbackTabs === 'function') {
await closeLocalhostCallbackTabs(normalizedUrl).catch(() => {});
}
return await pollContributionStatus({ reason: options.reason || 'submit_callback' });
} catch (error) {
await applyRuntimeUpdates({
contributionCallbackUrl: normalizedUrl,
contributionCallbackStatus: 'failed',
contributionCallbackMessage: `回调提交失败:${error.message}`,
});
if (typeof addLog === 'function') {
await addLog(`贡献模式:回调提交失败:${error.message}`, 'warn');
}
throw error;
const dedupeKey = `${sessionId}::${normalizedUrl}`;
if (pendingCallbackSubmissions.has(dedupeKey)) {
return pendingCallbackSubmissions.get(dedupeKey);
}
const task = (async () => {
await applyRuntimeUpdates({
contributionCallbackUrl: normalizedUrl,
contributionCallbackStatus: 'submitting',
contributionCallbackMessage: buildCallbackMessage('submitting'),
});
try {
const payload = await fetchContributionJson('/submit-callback', {
method: 'POST',
body: {
session_id: sessionId,
callback_url: normalizedUrl,
},
});
const nextStatus = 'submitted';
await applyRuntimeUpdates({
contributionCallbackUrl: normalizedUrl,
contributionCallbackStatus: nextStatus,
contributionCallbackMessage: buildCallbackMessage(nextStatus, payload),
});
if (typeof closeLocalhostCallbackTabs === 'function') {
await closeLocalhostCallbackTabs(normalizedUrl).catch(() => {});
}
return await pollContributionStatus({ reason: options.reason || 'submit_callback' });
} catch (error) {
await applyRuntimeUpdates({
contributionCallbackUrl: normalizedUrl,
contributionCallbackStatus: 'failed',
contributionCallbackMessage: `回调提交失败:${error.message}`,
});
if (typeof addLog === 'function') {
await addLog(`贡献模式:回调提交失败:${error.message}`, 'warn');
}
throw error;
} finally {
pendingCallbackSubmissions.delete(dedupeKey);
}
})();
pendingCallbackSubmissions.set(dedupeKey, task);
return task;
}
async function handleCapturedCallback(rawUrl, metadata = {}) {
@@ -487,6 +538,13 @@
}
const normalizedUrl = normalizeString(rawUrl);
const callbackDedupeKey = `${normalizeString(currentState.contributionSessionId)}::${normalizedUrl}`;
if (pendingCapturedCallbacks.has(callbackDedupeKey)) {
return pendingCapturedCallbacks.get(callbackDedupeKey);
}
if (pendingCallbackSubmissions.has(callbackDedupeKey)) {
return pendingCallbackSubmissions.get(callbackDedupeKey);
}
const currentCallbackStatus = normalizeContributionCallbackStatus(currentState.contributionCallbackStatus);
if (
normalizedUrl
@@ -496,24 +554,31 @@
return currentState;
}
await applyRuntimeUpdates({
contributionCallbackUrl: normalizedUrl,
contributionCallbackStatus: 'captured',
contributionCallbackMessage: buildCallbackMessage('captured'),
});
if (typeof addLog === 'function') {
await addLog(`贡献模式:已捕获回调地址(${metadata.source || 'unknown'})。`, 'info');
}
try {
return await submitContributionCallback(normalizedUrl, {
reason: metadata.source || 'navigation',
stateOverride: await getState(),
const task = (async () => {
await applyRuntimeUpdates({
contributionCallbackUrl: normalizedUrl,
contributionCallbackStatus: 'captured',
contributionCallbackMessage: buildCallbackMessage('captured'),
});
} catch {
return getState();
}
if (typeof addLog === 'function') {
await addLog(`贡献模式:已捕获回调地址(${metadata.source || 'unknown'})。`, 'info');
}
try {
return await submitContributionCallback(normalizedUrl, {
reason: metadata.source || 'navigation',
stateOverride: await getState(),
});
} catch {
return getState();
} finally {
pendingCapturedCallbacks.delete(callbackDedupeKey);
}
})();
pendingCapturedCallbacks.set(callbackDedupeKey, task);
return task;
}
async function pollContributionStatus(options = {}) {
@@ -594,13 +659,15 @@
return pollContributionStatus({ reason: 'resume_existing' });
}
const routingState = resolveContributionModeRoutingState(currentState);
const payload = await fetchContributionJson('/start', {
method: 'POST',
body: {
nickname: buildNickname(currentState, options.nickname),
qq: buildContributionQq(currentState, options.qq),
email: normalizeString(currentState.email),
source: 'cpa',
source: routingState.source,
target_group_name: routingState.targetGroupName,
channel: 'codex-extension',
},
});
+49 -3
View File
@@ -40,6 +40,9 @@
getPendingAutoRunTimerPlan,
getSourceLabel,
getState,
getStepDefinitionForState,
getStepIdsForState,
getLastStepIdForState,
getTabId,
getStopRequested,
handleAutoRunLoopUnhandledError,
@@ -127,7 +130,47 @@
}
}
function resolveLastStepIdForState(state = {}) {
if (typeof getLastStepIdForState === 'function') {
const fromResolver = Number(getLastStepIdForState(state));
if (Number.isFinite(fromResolver) && fromResolver > 0) {
return fromResolver;
}
}
const stepIds = typeof getStepIdsForState === 'function'
? (getStepIdsForState(state) || [])
: Object.keys(state?.stepStatuses || {}).map((step) => Number(step)).filter(Number.isFinite);
const sorted = stepIds.slice().sort((left, right) => left - right);
return Math.max(10, sorted[sorted.length - 1] || 0);
}
async function handleStepData(step, payload) {
if (payload.skipLoginVerificationStep || payload.directOAuthConsentPage) {
const latestState = await getState();
const currentDefinition = typeof getStepDefinitionForState === 'function'
? getStepDefinitionForState(step, latestState)
: null;
const currentKey = String(currentDefinition?.key || '').trim();
if (currentKey === 'oauth-login') {
const stepIds = typeof getStepIdsForState === 'function'
? (getStepIdsForState(latestState) || [])
: Object.keys(latestState?.stepStatuses || {}).map((item) => Number(item)).filter(Number.isFinite);
const ordered = stepIds.slice().sort((left, right) => left - right);
const currentIndex = ordered.indexOf(Number(step));
const nextStep = currentIndex >= 0 ? ordered[currentIndex + 1] : null;
const nextDefinition = Number.isFinite(nextStep) && typeof getStepDefinitionForState === 'function'
? getStepDefinitionForState(nextStep, latestState)
: null;
if (Number.isFinite(nextStep) && String(nextDefinition?.key || '').trim() === 'fetch-login-code') {
const nextStatus = latestState.stepStatuses?.[nextStep];
if (nextStatus !== 'running' && nextStatus !== 'completed' && nextStatus !== 'manual_completed') {
await setStepStatus(nextStep, 'skipped');
await addLog(`步骤 ${step}:检测到已直达 OAuth 授权页,已自动跳过步骤 ${nextStep} 登录验证码。`, 'warn');
}
}
}
}
switch (step) {
case 1: {
const updates = {};
@@ -217,7 +260,8 @@
broadcastDataUpdate({ localhostUrl: payload.localhostUrl });
}
break;
case 10: {
case 10:
case 13: {
if (payload.localhostUrl) {
await closeLocalhostCallbackTabs(payload.localhostUrl);
}
@@ -305,11 +349,13 @@
return { ok: true, error: errorMessage };
}
const completionState = message.step === 10 ? await getState() : null;
const latestState = await getState();
const lastStepId = resolveLastStepIdForState(latestState);
const completionState = message.step === lastStepId ? latestState : null;
await setStepStatus(message.step, 'completed');
await addLog(`步骤 ${message.step} 已完成`, 'ok');
await handleStepData(message.step, message.payload);
if (message.step === 10 && typeof appendAccountRunRecord === 'function') {
if (message.step === lastStepId && typeof appendAccountRunRecord === 'function') {
await appendAccountRunRecord('success', completionState);
}
notifyStepComplete(message.step, message.payload);
+10 -3
View File
@@ -26,6 +26,11 @@
return String(value || '').trim();
}
function resolvePlatformVerifyStep(state = {}) {
const visibleStep = Math.floor(Number(state?.visibleStep) || 0);
return visibleStep >= 10 ? visibleStep : 10;
}
function parseLocalhostCallback(rawUrl) {
let parsed;
try {
@@ -180,6 +185,7 @@
}
async function executeCpaStep10(state) {
const platformVerifyStep = resolvePlatformVerifyStep(state);
if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) {
throw new Error('步骤 9 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 9。');
}
@@ -192,7 +198,7 @@
if (shouldBypassStep9ForLocalCpa(state)) {
await addLog('步骤 10:检测到本地 CPA,且当前策略为“跳过第10步”,本轮不再重复提交回调地址。', 'info');
await completeStepFromBackground(10, {
await completeStepFromBackground(platformVerifyStep, {
localhostUrl: state.localhostUrl,
verifiedStatus: 'local-auto',
});
@@ -225,7 +231,7 @@
|| normalizeString(result?.status_message)
|| 'CPA 已通过接口提交回调';
await addLog(`步骤 10${verifiedStatus}`, 'ok');
await completeStepFromBackground(10, {
await completeStepFromBackground(platformVerifyStep, {
localhostUrl: callback.url,
verifiedStatus,
});
@@ -237,6 +243,7 @@
}
async function executeCodex2ApiStep10(state) {
const platformVerifyStep = resolvePlatformVerifyStep(state);
if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) {
throw new Error('步骤 9 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 9。');
}
@@ -272,7 +279,7 @@
const verifiedStatus = normalizeString(result?.message) || 'Codex2API OAuth 账号添加成功';
await addLog(`步骤 10${verifiedStatus}`, 'ok');
await completeStepFromBackground(10, {
await completeStepFromBackground(platformVerifyStep, {
localhostUrl: callback.url,
verifiedStatus,
});