fix: harden CPA auth flow with API callback/state checks

Move CPA oauth url/callback handling to management API first, persist state/origin runtime keys, and fail fast on invalid callback or missing management auth to reduce flaky panel-driven auth failures.
This commit is contained in:
cnxwzy
2026-04-27 19:13:22 +08:00
parent c98cfd7053
commit f9f8df19d3
12 changed files with 911 additions and 2294 deletions
+43 -122
View File
@@ -3,19 +3,13 @@
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundContributionOAuthModule() {
const API_BASE_URL = 'https://apikey.qzz.io/oauth/api';
const ACTIVE_STATUSES = new Set(['started', 'waiting', 'processing']);
const FINAL_STATUSES = new Set(['auto_approved', 'auto_rejected', 'expired', 'error']);
const FINAL_STATUSES = new Set(['auto_approved', 'auto_rejected', 'manual_review_required', 'expired', 'error']);
const CALLBACK_FINAL_STATUSES = new Set(['submitted']);
const CALLBACK_WAITING_STATUSES = new Set(['idle', 'waiting', 'captured', 'failed', 'submitting']);
const CONTRIBUTION_SOURCE_CPA = 'cpa';
const CONTRIBUTION_SOURCE_SUB2API = 'sub2api';
const CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME = 'codex号池';
const CONTRIBUTION_SUB2API_PLUS_GROUP_NAME = 'openai-plus';
const RUNTIME_DEFAULTS = {
contributionMode: false,
contributionModeExpected: false,
contributionSource: CONTRIBUTION_SOURCE_SUB2API,
contributionTargetGroupName: CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME,
contributionNickname: '',
contributionQq: '',
contributionSessionId: '',
@@ -44,7 +38,6 @@
} = deps;
let listenersBound = false;
const inFlightCapturedCallbackTasks = new Map();
function normalizeString(value = '') {
return String(value || '').trim();
@@ -71,6 +64,9 @@
case 'auto_rejected':
case 'rejected':
return 'auto_rejected';
case 'manual_review_required':
case 'manual_review':
return 'manual_review_required';
case 'expired':
case 'timeout':
return 'expired';
@@ -111,63 +107,6 @@
return FINAL_STATUSES.has(normalizeContributionStatus(status));
}
function runCapturedCallbackOnce(callbackUrl, executor) {
const normalizedUrl = normalizeString(callbackUrl);
if (!normalizedUrl) {
return Promise.resolve().then(executor);
}
const existingTask = inFlightCapturedCallbackTasks.get(normalizedUrl);
if (existingTask) {
return existingTask;
}
let task = null;
task = Promise.resolve()
.then(executor)
.finally(() => {
if (inFlightCapturedCallbackTasks.get(normalizedUrl) === task) {
inFlightCapturedCallbackTasks.delete(normalizedUrl);
}
});
inFlightCapturedCallbackTasks.set(normalizedUrl, task);
return task;
}
function normalizeContributionSource(value = '') {
const normalized = normalizeString(value).toLowerCase();
return normalized === CONTRIBUTION_SOURCE_SUB2API
? CONTRIBUTION_SOURCE_SUB2API
: CONTRIBUTION_SOURCE_CPA;
}
function resolveContributionRouting(state = {}) {
const currentStatus = normalizeContributionStatus(state.contributionStatus);
const currentSource = normalizeContributionSource(state.contributionSource);
const hasActiveSession = Boolean(
normalizeString(state.contributionSessionId)
&& currentStatus
&& !FINAL_STATUSES.has(currentStatus)
);
if (hasActiveSession) {
return {
source: currentSource,
targetGroupName: currentSource === CONTRIBUTION_SOURCE_SUB2API
? (normalizeString(state.contributionTargetGroupName) || CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME)
: '',
};
}
const source = CONTRIBUTION_SOURCE_SUB2API;
return {
source,
targetGroupName: Boolean(state.plusModeEnabled)
? CONTRIBUTION_SUB2API_PLUS_GROUP_NAME
: (normalizeString(state.contributionTargetGroupName) || CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME),
};
}
function getStatusLabel(status = '') {
switch (normalizeContributionStatus(status)) {
case 'started':
@@ -175,11 +114,13 @@
case 'waiting':
return '等待提交回调';
case 'processing':
return '已提交回调,等待服务端确认';
return '已提交回调,等待 CPA 确认';
case 'auto_approved':
return '贡献成功,服务端已确认';
return '贡献成功,CPA 已确认';
case 'auto_rejected':
return '贡献未通过确认';
case 'manual_review_required':
return '已提交,等待人工处理';
case 'expired':
return '贡献会话已超时';
case 'error':
@@ -537,44 +478,42 @@
}
async function handleCapturedCallback(rawUrl, metadata = {}) {
const currentState = await getState();
if (!normalizeString(currentState.contributionSessionId) || !currentState.contributionMode) {
return currentState;
}
if (!isContributionCallbackUrl(rawUrl, currentState)) {
return currentState;
}
const normalizedUrl = normalizeString(rawUrl);
return runCapturedCallbackOnce(normalizedUrl, async () => {
const currentState = await getState();
if (!normalizeString(currentState.contributionSessionId) || !currentState.contributionMode) {
return currentState;
}
if (!isContributionCallbackUrl(normalizedUrl, currentState)) {
return currentState;
}
const currentCallbackStatus = normalizeContributionCallbackStatus(currentState.contributionCallbackStatus);
if (
normalizedUrl
&& normalizeString(currentState.contributionCallbackUrl) === normalizedUrl
&& (CALLBACK_FINAL_STATUSES.has(currentCallbackStatus) || currentCallbackStatus === 'submitting')
) {
return currentState;
}
const currentCallbackStatus = normalizeContributionCallbackStatus(currentState.contributionCallbackStatus);
if (
normalizedUrl
&& normalizeString(currentState.contributionCallbackUrl) === normalizedUrl
&& (CALLBACK_FINAL_STATUSES.has(currentCallbackStatus) || currentCallbackStatus === 'submitting')
) {
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(),
});
} catch {
return getState();
}
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(),
});
} catch {
return getState();
}
}
async function pollContributionStatus(options = {}) {
@@ -597,16 +536,6 @@
const callbackState = deriveCallbackState(mergedPayload, currentState);
const updates = {
contributionLastPollAt: Date.now(),
contributionSource: normalizeContributionSource(
mergedPayload.source
|| mergedPayload.source_kind
|| currentState.contributionSource
),
contributionTargetGroupName: normalizeString(
mergedPayload.target_group_name
|| mergedPayload.group_name
|| currentState.contributionTargetGroupName
),
contributionStatus: normalizedStatus,
contributionStatusMessage: buildStatusMessage(normalizedStatus, mergedPayload),
contributionCallbackUrl: callbackState.callbackUrl,
@@ -648,7 +577,6 @@
async function startContributionFlow(options = {}) {
const currentState = options.stateOverride || await getState();
const shouldOpenAuthTab = options.openAuthTab !== false;
const routing = resolveContributionRouting(currentState);
if (!currentState.contributionMode) {
throw new Error('请先进入贡献模式。');
}
@@ -672,8 +600,7 @@
nickname: buildNickname(currentState, options.nickname),
qq: buildContributionQq(currentState, options.qq),
email: normalizeString(currentState.email),
source: routing.source,
target_group_name: routing.targetGroupName,
source: 'cpa',
channel: 'codex-extension',
},
});
@@ -686,12 +613,6 @@
}
await applyRuntimeUpdates({
contributionSource: normalizeContributionSource(payload.source || routing.source),
contributionTargetGroupName: normalizeString(
payload.target_group_name
|| payload.group_name
|| routing.targetGroupName
),
contributionSessionId: sessionId,
contributionAuthUrl: authUrl,
contributionAuthState: authState,
@@ -722,7 +643,7 @@
}
function onTabUpdated(tabId, changeInfo, tab) {
const candidateUrl = normalizeString(changeInfo?.url);
const candidateUrl = normalizeString(changeInfo?.url || tab?.url);
if (!candidateUrl) {
return;
}