重构 OAuth 总超时为 CPA 来源专属策略

This commit is contained in:
QLHazyCoder
2026-05-24 20:53:32 +08:00
parent 3a60560e27
commit a1793334db
21 changed files with 111 additions and 88 deletions
+51 -6
View File
@@ -48,6 +48,33 @@ function extractFunction(name) {
return source.slice(start, end);
}
const OAUTH_TIMEOUT_BUDGET_TEST_REGISTRY = `
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
const self = {
MultiPageFlowRegistry: {
normalizeFlowId(value, fallback = 'openai') {
const normalized = String(value || '').trim().toLowerCase();
return ['openai', 'kiro', 'grok'].includes(normalized) ? normalized : fallback;
},
normalizeTargetId(flowId, value, fallback = 'cpa') {
const normalized = String(value || '').trim().toLowerCase();
if (flowId === 'openai' && ['cpa', 'sub2api', 'codex2api'].includes(normalized)) return normalized;
if (flowId === 'kiro') return 'kiro-rs';
if (flowId === 'grok') return 'webchat2api';
return fallback;
},
getDefaultTargetId(flowId) {
return flowId === 'openai' ? 'cpa' : '';
},
getTargetCapabilities(flowId, targetId) {
return flowId === 'openai' && targetId === 'cpa'
? { usesOauthTimeoutBudget: true }
: { usesOauthTimeoutBudget: false };
},
},
};
`;
test('background auth chain set does not include Plus session import nodes', () => {
const authChainStart = source.indexOf('const AUTH_CHAIN_NODE_IDS = new Set([');
const authChainEnd = source.indexOf(']);', authChainStart);
@@ -431,6 +458,9 @@ test('oauth timeout budget ignores stale deadlines from an old oauth url', async
const api = new Function(`
const LOG_PREFIX = '[test]';
const OAUTH_FLOW_TIMEOUT_MS = 5 * 60 * 1000;
${OAUTH_TIMEOUT_BUDGET_TEST_REGISTRY}
${extractFunction('resolveOAuthTimeoutBudgetScope')}
${extractFunction('shouldUseOAuthTimeoutBudget')}
${extractFunction('normalizeOAuthFlowDeadlineAt')}
${extractFunction('normalizeOAuthFlowSourceUrl')}
${extractFunction('getOAuthFlowRemainingMs')}
@@ -444,6 +474,8 @@ return {
step: 8,
actionLabel: '登录验证码流程',
state: {
activeFlowId: 'openai',
targetId: 'cpa',
oauthUrl: 'https://oauth.example/current',
oauthFlowDeadlineAt: Date.now() + 1200,
oauthFlowDeadlineSourceUrl: 'https://oauth.example/old',
@@ -457,7 +489,10 @@ test('oauth timeout budget clamps local timeout when enabled by default', async
const api = new Function(`
const LOG_PREFIX = '[test]';
const OAUTH_FLOW_TIMEOUT_MS = 5 * 60 * 1000;
${OAUTH_TIMEOUT_BUDGET_TEST_REGISTRY}
${extractFunction('buildOAuthFlowTimeoutError')}
${extractFunction('resolveOAuthTimeoutBudgetScope')}
${extractFunction('shouldUseOAuthTimeoutBudget')}
${extractFunction('normalizeOAuthFlowDeadlineAt')}
${extractFunction('normalizeOAuthFlowSourceUrl')}
${extractFunction('getOAuthFlowRemainingMs')}
@@ -471,6 +506,8 @@ return {
step: 8,
actionLabel: '登录验证码流程',
state: {
activeFlowId: 'openai',
targetId: 'cpa',
oauthUrl: 'https://oauth.example/current',
oauthFlowDeadlineAt: Date.now() + 1200,
oauthFlowDeadlineSourceUrl: 'https://oauth.example/current',
@@ -481,11 +518,14 @@ return {
assert(timeoutMs >= 1000);
});
test('oauth timeout budget disabled mode ignores active deadlines', async () => {
test('oauth timeout budget ignores active deadlines outside openai cpa target', async () => {
const api = new Function(`
const LOG_PREFIX = '[test]';
const OAUTH_FLOW_TIMEOUT_MS = 5 * 60 * 1000;
${OAUTH_TIMEOUT_BUDGET_TEST_REGISTRY}
${extractFunction('buildOAuthFlowTimeoutError')}
${extractFunction('resolveOAuthTimeoutBudgetScope')}
${extractFunction('shouldUseOAuthTimeoutBudget')}
${extractFunction('normalizeOAuthFlowDeadlineAt')}
${extractFunction('normalizeOAuthFlowSourceUrl')}
${extractFunction('getOAuthFlowRemainingMs')}
@@ -499,7 +539,8 @@ return {
step: 9,
actionLabel: 'OAuth localhost 回调',
state: {
oauthFlowTimeoutEnabled: false,
activeFlowId: 'openai',
targetId: 'sub2api',
oauthUrl: 'https://oauth.example/current',
oauthFlowDeadlineAt: Date.now() - 1000,
oauthFlowDeadlineSourceUrl: 'https://oauth.example/current',
@@ -509,16 +550,18 @@ return {
assert.equal(timeoutMs, 15000);
});
test('startOAuthFlowTimeoutWindow clears stale deadline when timeout is disabled', async () => {
test('startOAuthFlowTimeoutWindow clears stale deadline outside openai cpa target', async () => {
const events = {
stateUpdates: [],
logs: [],
};
const api = new Function('events', `
const api = new Function('events', `
const OAUTH_FLOW_TIMEOUT_MS = 5 * 60 * 1000;
${OAUTH_TIMEOUT_BUDGET_TEST_REGISTRY}
async function getState() {
return {
oauthFlowTimeoutEnabled: false,
activeFlowId: 'openai',
targetId: 'sub2api',
oauthFlowDeadlineAt: Date.now() - 1000,
oauthFlowDeadlineSourceUrl: 'https://oauth.example/old',
};
@@ -529,6 +572,8 @@ async function setState(update) {
async function addLog(message, level) {
events.logs.push({ message, level });
}
${extractFunction('resolveOAuthTimeoutBudgetScope')}
${extractFunction('shouldUseOAuthTimeoutBudget')}
${extractFunction('normalizeOAuthFlowSourceUrl')}
${extractFunction('startOAuthFlowTimeoutWindow')}
return {
@@ -546,7 +591,7 @@ return {
oauthFlowDeadlineAt: null,
oauthFlowDeadlineSourceUrl: null,
}]);
assert.match(events.logs[0].message, /授权后链总超时已关闭/);
assert.deepStrictEqual(events.logs, []);
});
test('oauth localhost timeout recovery resumes from bound-email relogin tail when present', async () => {
@@ -156,12 +156,11 @@ return {
});
});
test('oauth flow timeout setting is persisted and normalized as boolean', () => {
test('cloudflare temp email settings payload ignores removed UI-only flags', () => {
const api = new Function(`
const DEFAULT_VERIFICATION_RESEND_COUNT = 4;
const PERSISTED_SETTING_DEFAULTS = {
panelMode: 'cpa',
oauthFlowTimeoutEnabled: true,
autoStepDelaySeconds: null,
verificationResendCount: DEFAULT_VERIFICATION_RESEND_COUNT,
mailProvider: '163',
@@ -222,15 +221,10 @@ return {
};
`)();
assert.equal(api.normalizePersistentSettingValue('oauthFlowTimeoutEnabled', 0), false);
assert.equal(api.normalizePersistentSettingValue('oauthFlowTimeoutEnabled', 1), true);
assert.deepEqual(api.buildPersistentSettingsPayload({
oauthFlowTimeoutEnabled: false,
}), {
oauthFlowTimeoutEnabled: false,
});
removedUiOnlyFlag: false,
}), {});
const defaults = api.buildPersistentSettingsPayload({}, { fillDefaults: true });
assert.equal(defaults.oauthFlowTimeoutEnabled, true);
assert.equal(Object.prototype.hasOwnProperty.call(defaults, 'removedUiOnlyFlag'), false);
});
+2
View File
@@ -27,6 +27,7 @@ test('flow capability registry keeps OpenAI phone signup available only when run
assert.equal(enabledState.canUsePhoneSignup, true);
assert.equal(enabledState.effectiveSignupMethod, 'phone');
assert.equal(enabledState.shouldWarnCpaPhoneSignup, true);
assert.equal(enabledState.targetCapabilities.usesOauthTimeoutBudget, true);
assert.deepEqual(enabledState.effectiveSignupMethods, ['email', 'phone']);
const plusLockedState = registry.resolveSidepanelCapabilities({
@@ -43,6 +44,7 @@ test('flow capability registry keeps OpenAI phone signup available only when run
assert.equal(plusLockedState.canUsePhoneSignup, false);
assert.equal(plusLockedState.effectiveSignupMethod, 'email');
assert.equal(plusLockedState.shouldWarnCpaPhoneSignup, false);
assert.equal(plusLockedState.targetCapabilities.usesOauthTimeoutBudget, false);
assert.deepEqual(plusLockedState.effectiveSignupMethods, ['email']);
});
@@ -29,6 +29,14 @@ test('flow registry exposes canonical flow and target metadata', () => {
flowRegistry.getFlowDefinition('openai')?.targets?.cpa?.defaultState,
{ vpsUrl: '', vpsPassword: '', localCpaStep9Mode: 'submit' }
);
assert.equal(
flowRegistry.getTargetCapabilities('openai', 'cpa')?.usesOauthTimeoutBudget,
true
);
assert.equal(
flowRegistry.getTargetCapabilities('openai', 'sub2api')?.usesOauthTimeoutBudget,
undefined
);
assert.deepEqual(
flowRegistry.getFlowDefinition('kiro')?.targets?.['kiro-rs']?.defaultState,
{ baseUrl: '', apiKey: '' }
@@ -248,7 +248,6 @@ const inputTempEmailUseRandomSubdomain = { checked: true };
const inputAutoSkipFailures = { checked: false };
const inputAutoSkipFailuresThreadIntervalMinutes = { value: '5' };
const inputAutoStepDelaySeconds = { value: '10' };
const inputOAuthFlowTimeoutEnabled = { checked: true };
const inputVerificationResendCount = { value: '6' };
const DEFAULT_VERIFICATION_RESEND_COUNT = 4;
const PHONE_SMS_PROVIDER_HERO_SMS = 'hero-sms';
-2
View File
@@ -133,7 +133,6 @@ const inputTempEmailUseRandomSubdomain = { checked: false };
const inputAutoSkipFailures = { checked: false };
const inputAutoSkipFailuresThreadIntervalMinutes = { value: '0' };
const inputAutoStepDelaySeconds = { value: '' };
const inputOAuthFlowTimeoutEnabled = { checked: true };
const inputVerificationResendCount = { value: '4' };
const DEFAULT_VERIFICATION_RESEND_COUNT = 4;
const PHONE_SMS_PROVIDER_HERO_SMS = 'hero-sms';
@@ -406,7 +405,6 @@ const inputLuckmailDomain = { value: '' };
const inputAutoSkipFailures = { checked: false };
const inputAutoSkipFailuresThreadIntervalMinutes = { value: '' };
const inputAutoStepDelaySeconds = { value: '' };
const inputOAuthFlowTimeoutEnabled = { checked: true };
const inputVerificationResendCount = { value: '' };
const inputPhoneVerificationEnabled = { checked: false };
const selectPhoneSmsProvider = { value: 'hero-sms' };
@@ -204,7 +204,6 @@ const inputTempEmailUseRandomSubdomain = { checked: false };
const inputAutoSkipFailures = { checked: false };
const inputAutoSkipFailuresThreadIntervalMinutes = { value: '0' };
const inputAutoStepDelaySeconds = { value: '' };
const inputOAuthFlowTimeoutEnabled = { checked: true };
const inputVerificationResendCount = { value: '4' };
const DEFAULT_VERIFICATION_RESEND_COUNT = 4;
const PHONE_SMS_PROVIDER_HERO_SMS = 'hero-sms';
+3 -4
View File
@@ -48,7 +48,6 @@ test('sidepanel splits shared auto-run controls from openai oauth controls', ()
const step6CookieIndex = html.indexOf('id="row-step6-cookie-settings"');
const sharedAutoRunIndex = html.indexOf('id="row-shared-auto-run"');
const threadIntervalIndex = html.indexOf('id="row-auto-run-thread-interval"');
const oauthTimeoutIndex = html.indexOf('id="row-oauth-flow-timeout"');
const stepRangeIndex = html.indexOf('id="row-step-execution-range"');
const oauthDisplayIndex = html.indexOf('id="row-oauth-display"');
const oauthCallbackIndex = html.indexOf('id="row-oauth-callback"');
@@ -57,15 +56,15 @@ test('sidepanel splits shared auto-run controls from openai oauth controls', ()
assert.notEqual(step6CookieIndex, -1);
assert.notEqual(sharedAutoRunIndex, -1);
assert.notEqual(threadIntervalIndex, -1);
assert.notEqual(oauthTimeoutIndex, -1);
assert.doesNotMatch(html, /id="row-oauth-flow-timeout"/);
assert.doesNotMatch(html, /id="input-oauth-flow-timeout-enabled"/);
assert.notEqual(stepRangeIndex, -1);
assert.notEqual(oauthDisplayIndex, -1);
assert.notEqual(oauthCallbackIndex, -1);
assert.notEqual(settingsActionsIndex, -1);
assert.ok(sharedAutoRunIndex > step6CookieIndex, 'shared auto-run should render below the openai step6 cookie row');
assert.ok(threadIntervalIndex > sharedAutoRunIndex, 'thread interval should be part of the shared auto-run block');
assert.ok(threadIntervalIndex < oauthTimeoutIndex, 'thread interval should stay outside openai oauth controls');
assert.ok(stepRangeIndex > oauthTimeoutIndex, 'step execution range should render below oauth timeout');
assert.ok(stepRangeIndex > threadIntervalIndex, 'step execution range should render below shared thread interval');
assert.ok(stepRangeIndex < oauthDisplayIndex, 'step execution range should stay above oauth runtime display');
assert.ok(oauthCallbackIndex > oauthDisplayIndex, 'openai callback row should follow the oauth display');
assert.ok(settingsActionsIndex > oauthCallbackIndex, 'save settings action should live outside the callback row');