Fix dynamic auth tail node propagation

This commit is contained in:
QLHazyCoder
2026-05-20 03:14:18 +08:00
parent 5d7f78617b
commit fbb66fb5a2
7 changed files with 153 additions and 9 deletions
+4 -1
View File
@@ -212,7 +212,10 @@
{
type: 'SUBMIT_ADD_EMAIL',
source: 'background',
payload: { email: resolvedEmail },
payload: {
email: resolvedEmail,
nodeId: state?.nodeId || activeFetchLoginCodeStepKey || 'fetch-login-code',
},
},
{
timeoutMs,
+1 -1
View File
@@ -307,7 +307,7 @@
'signup-page',
{
type: 'EXECUTE_NODE',
nodeId: 'oauth-login',
nodeId: state?.nodeId || 'oauth-login',
step: 7,
source: 'background',
payload: {
+3 -3
View File
@@ -94,15 +94,15 @@
}
const code = normalizeString(parsed.searchParams.get('code'));
const state = normalizeString(parsed.searchParams.get('state'));
if (!code || !state) {
const oauthState = normalizeString(parsed.searchParams.get('state'));
if (!code || !oauthState) {
throw new Error(`步骤 ${platformVerifyStep} 捕获到的 localhost OAuth 回调地址缺少 code 或 state,请重新执行步骤 ${confirmOauthStep}`);
}
return {
url: parsed.toString(),
code,
state,
state: oauthState,
};
}
+3 -3
View File
@@ -503,15 +503,15 @@
}
const code = normalizeString(parsed.searchParams.get('code'));
const state = normalizeString(parsed.searchParams.get('state'));
if (!code || !state) {
const oauthState = normalizeString(parsed.searchParams.get('state'));
if (!code || !oauthState) {
throw new Error('回调 URL 中缺少 code 或 state。');
}
return {
url: parsed.toString(),
code,
state,
state: oauthState,
};
}
+3 -1
View File
@@ -1117,6 +1117,7 @@
}
await chrome.tabs.update(signupTabId, { active: true });
const completionNodeId = await getNodeIdForStep(completionStep);
const baseResponseTimeoutMs = await getResponseTimeoutMsForStep(
step,
step === 8
@@ -1134,6 +1135,7 @@
source: 'background',
payload: {
code,
...(completionNodeId ? { nodeId: completionNodeId } : {}),
...(step === 4 && options.signupProfile ? { signupProfile: options.signupProfile } : {}),
},
};
@@ -1268,6 +1270,7 @@
async function resolveVerificationStep(step, state, mail, options = {}) {
const completionStep = getCompletionStep(step, options);
activeVerificationLogStep = completionStep;
const completionNodeId = await getNodeIdForStep(completionStep);
const stateKey = getVerificationCodeStateKey(step);
const rejectedCodes = new Set();
const hotmailPollConfig = mail.provider === HOTMAIL_PROVIDER
@@ -1419,7 +1422,6 @@
[stateKey]: result.code,
});
const completionNodeId = await getNodeIdForStep(completionStep);
if (!completionNodeId) {
throw new Error(`步骤 ${completionStep} 未映射到验证码节点。`);
}
+1
View File
@@ -78,6 +78,7 @@ const SIGNUP_PAGE_NODE_HANDLERS = Object.freeze({
'fill-password': (payload) => step3_fillEmailPassword(payload),
'fill-profile': (payload) => step5_fillNameBirthday(payload),
'oauth-login': (payload) => step6_login(payload),
'relogin-bound-email': (payload) => step6_login(payload),
'confirm-oauth': (_payload) => step8_findAndClick(),
});
@@ -0,0 +1,138 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep10;`)({});
function createExecutor(overrides = {}) {
const events = {
logs: [],
completed: [],
fetchCalls: [],
};
const executor = api.createStep10Executor({
addLog: async (message, level, options) => {
events.logs.push({ message, level, options });
},
chrome: {},
closeConflictingTabsForSource: async () => {},
completeNodeFromBackground: async (nodeId, payload) => {
events.completed.push({ nodeId, payload });
},
ensureContentScriptReadyOnTab: async () => {},
getPanelMode: () => 'cpa',
getStepIdByKeyForState: (stepKey) => ({
'oauth-login': 7,
'relogin-bound-email': 11,
'confirm-oauth': 14,
'platform-verify': 15,
})[stepKey] || null,
getTabId: async () => 1,
isLocalhostOAuthCallbackUrl: (url) => {
try {
const parsed = new URL(url);
return ['localhost', '127.0.0.1'].includes(parsed.hostname)
&& Boolean(parsed.searchParams.get('code'))
&& Boolean(parsed.searchParams.get('state'));
} catch {
return false;
}
},
isTabAlive: async () => true,
normalizeCodex2ApiUrl: (url) => url,
normalizeSub2ApiUrl: (url) => url,
rememberSourceLastUrl: () => {},
reuseOrCreateTab: async () => 1,
sendToContentScript: async () => ({}),
sendToContentScriptResilient: async () => ({}),
shouldBypassStep9ForLocalCpa: () => false,
SUB2API_STEP9_RESPONSE_TIMEOUT_MS: 30000,
...overrides,
});
return { executor, events };
}
test('CPA platform verify resolves relogin tail steps from active definitions', async () => {
const originalFetch = global.fetch;
const { executor, events } = createExecutor();
global.fetch = async (url, options = {}) => {
events.fetchCalls.push({ url, body: JSON.parse(options.body || '{}') });
return {
ok: true,
status: 200,
json: async () => ({ message: 'CPA callback accepted' }),
};
};
try {
await executor.executeStep10({
visibleStep: 15,
nodeId: 'platform-verify',
localhostUrl: 'http://127.0.0.1:8317/codex/callback?code=abc&state=expected',
cpaOAuthState: 'expected',
cpaManagementOrigin: 'http://127.0.0.1:8317',
vpsUrl: 'http://127.0.0.1:8317/management.html#/oauth',
vpsPassword: 'secret',
});
} finally {
global.fetch = originalFetch;
}
assert.equal(events.fetchCalls.length, 1);
assert.equal(events.fetchCalls[0].body.redirect_url, 'http://127.0.0.1:8317/codex/callback?code=abc&state=expected');
assert.deepStrictEqual(events.completed, [
{
nodeId: 'platform-verify',
payload: {
localhostUrl: 'http://127.0.0.1:8317/codex/callback?code=abc&state=expected',
verifiedStatus: 'CPA callback accepted',
},
},
]);
});
test('CPA state mismatch points relogin-enabled flow back to relogin-bound-email step', async () => {
const { executor } = createExecutor();
await assert.rejects(
() => executor.executeStep10({
visibleStep: 15,
nodeId: 'platform-verify',
localhostUrl: 'http://127.0.0.1:8317/codex/callback?code=abc&state=actual',
cpaOAuthState: 'expected',
cpaManagementOrigin: 'http://127.0.0.1:8317',
vpsUrl: 'http://127.0.0.1:8317/management.html#/oauth',
vpsPassword: 'secret',
}),
(error) => {
assert.match(error.message, /11/);
assert.doesNotMatch(error.message, /步骤\s*10|姝ラ.*10/);
return true;
}
);
});
test('CPA invalid localhost callback points to the dynamic confirm-oauth step', async () => {
const { executor } = createExecutor({
isLocalhostOAuthCallbackUrl: () => false,
});
await assert.rejects(
() => executor.executeStep10({
visibleStep: 15,
nodeId: 'platform-verify',
localhostUrl: 'notaurl',
cpaManagementOrigin: 'http://127.0.0.1:8317',
vpsUrl: 'http://127.0.0.1:8317/management.html#/oauth',
vpsPassword: 'secret',
}),
(error) => {
assert.match(error.message, /14/);
assert.doesNotMatch(error.message, /12/);
return true;
}
);
});