Fix dynamic auth tail node propagation
This commit is contained in:
@@ -212,7 +212,10 @@
|
|||||||
{
|
{
|
||||||
type: 'SUBMIT_ADD_EMAIL',
|
type: 'SUBMIT_ADD_EMAIL',
|
||||||
source: 'background',
|
source: 'background',
|
||||||
payload: { email: resolvedEmail },
|
payload: {
|
||||||
|
email: resolvedEmail,
|
||||||
|
nodeId: state?.nodeId || activeFetchLoginCodeStepKey || 'fetch-login-code',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
timeoutMs,
|
timeoutMs,
|
||||||
|
|||||||
@@ -307,7 +307,7 @@
|
|||||||
'signup-page',
|
'signup-page',
|
||||||
{
|
{
|
||||||
type: 'EXECUTE_NODE',
|
type: 'EXECUTE_NODE',
|
||||||
nodeId: 'oauth-login',
|
nodeId: state?.nodeId || 'oauth-login',
|
||||||
step: 7,
|
step: 7,
|
||||||
source: 'background',
|
source: 'background',
|
||||||
payload: {
|
payload: {
|
||||||
|
|||||||
@@ -94,15 +94,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const code = normalizeString(parsed.searchParams.get('code'));
|
const code = normalizeString(parsed.searchParams.get('code'));
|
||||||
const state = normalizeString(parsed.searchParams.get('state'));
|
const oauthState = normalizeString(parsed.searchParams.get('state'));
|
||||||
if (!code || !state) {
|
if (!code || !oauthState) {
|
||||||
throw new Error(`步骤 ${platformVerifyStep} 捕获到的 localhost OAuth 回调地址缺少 code 或 state,请重新执行步骤 ${confirmOauthStep}。`);
|
throw new Error(`步骤 ${platformVerifyStep} 捕获到的 localhost OAuth 回调地址缺少 code 或 state,请重新执行步骤 ${confirmOauthStep}。`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
url: parsed.toString(),
|
url: parsed.toString(),
|
||||||
code,
|
code,
|
||||||
state,
|
state: oauthState,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -503,15 +503,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const code = normalizeString(parsed.searchParams.get('code'));
|
const code = normalizeString(parsed.searchParams.get('code'));
|
||||||
const state = normalizeString(parsed.searchParams.get('state'));
|
const oauthState = normalizeString(parsed.searchParams.get('state'));
|
||||||
if (!code || !state) {
|
if (!code || !oauthState) {
|
||||||
throw new Error('回调 URL 中缺少 code 或 state。');
|
throw new Error('回调 URL 中缺少 code 或 state。');
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
url: parsed.toString(),
|
url: parsed.toString(),
|
||||||
code,
|
code,
|
||||||
state,
|
state: oauthState,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1117,6 +1117,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
await chrome.tabs.update(signupTabId, { active: true });
|
await chrome.tabs.update(signupTabId, { active: true });
|
||||||
|
const completionNodeId = await getNodeIdForStep(completionStep);
|
||||||
const baseResponseTimeoutMs = await getResponseTimeoutMsForStep(
|
const baseResponseTimeoutMs = await getResponseTimeoutMsForStep(
|
||||||
step,
|
step,
|
||||||
step === 8
|
step === 8
|
||||||
@@ -1134,6 +1135,7 @@
|
|||||||
source: 'background',
|
source: 'background',
|
||||||
payload: {
|
payload: {
|
||||||
code,
|
code,
|
||||||
|
...(completionNodeId ? { nodeId: completionNodeId } : {}),
|
||||||
...(step === 4 && options.signupProfile ? { signupProfile: options.signupProfile } : {}),
|
...(step === 4 && options.signupProfile ? { signupProfile: options.signupProfile } : {}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -1268,6 +1270,7 @@
|
|||||||
async function resolveVerificationStep(step, state, mail, options = {}) {
|
async function resolveVerificationStep(step, state, mail, options = {}) {
|
||||||
const completionStep = getCompletionStep(step, options);
|
const completionStep = getCompletionStep(step, options);
|
||||||
activeVerificationLogStep = completionStep;
|
activeVerificationLogStep = completionStep;
|
||||||
|
const completionNodeId = await getNodeIdForStep(completionStep);
|
||||||
const stateKey = getVerificationCodeStateKey(step);
|
const stateKey = getVerificationCodeStateKey(step);
|
||||||
const rejectedCodes = new Set();
|
const rejectedCodes = new Set();
|
||||||
const hotmailPollConfig = mail.provider === HOTMAIL_PROVIDER
|
const hotmailPollConfig = mail.provider === HOTMAIL_PROVIDER
|
||||||
@@ -1419,7 +1422,6 @@
|
|||||||
[stateKey]: result.code,
|
[stateKey]: result.code,
|
||||||
});
|
});
|
||||||
|
|
||||||
const completionNodeId = await getNodeIdForStep(completionStep);
|
|
||||||
if (!completionNodeId) {
|
if (!completionNodeId) {
|
||||||
throw new Error(`步骤 ${completionStep} 未映射到验证码节点。`);
|
throw new Error(`步骤 ${completionStep} 未映射到验证码节点。`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ const SIGNUP_PAGE_NODE_HANDLERS = Object.freeze({
|
|||||||
'fill-password': (payload) => step3_fillEmailPassword(payload),
|
'fill-password': (payload) => step3_fillEmailPassword(payload),
|
||||||
'fill-profile': (payload) => step5_fillNameBirthday(payload),
|
'fill-profile': (payload) => step5_fillNameBirthday(payload),
|
||||||
'oauth-login': (payload) => step6_login(payload),
|
'oauth-login': (payload) => step6_login(payload),
|
||||||
|
'relogin-bound-email': (payload) => step6_login(payload),
|
||||||
'confirm-oauth': (_payload) => step8_findAndClick(),
|
'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;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user