fix(auth): preserve phone identity across add-email retries
This commit is contained in:
@@ -9014,6 +9014,15 @@ async function handleStepData(step, payload) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 7:
|
case 7:
|
||||||
|
if (payload.accountIdentifierType || payload.accountIdentifier || payload.signupPhoneNumber || payload.signupPhoneActivation || payload.signupPhoneCompletedActivation) {
|
||||||
|
await setState({
|
||||||
|
accountIdentifierType: payload.accountIdentifierType || null,
|
||||||
|
accountIdentifier: String(payload.accountIdentifier || '').trim(),
|
||||||
|
signupPhoneNumber: String(payload.signupPhoneNumber || '').trim(),
|
||||||
|
signupPhoneActivation: payload.signupPhoneActivation || null,
|
||||||
|
signupPhoneCompletedActivation: payload.signupPhoneCompletedActivation || null,
|
||||||
|
});
|
||||||
|
}
|
||||||
if (payload.loginVerificationRequestedAt) {
|
if (payload.loginVerificationRequestedAt) {
|
||||||
await setState({ loginVerificationRequestedAt: payload.loginVerificationRequestedAt });
|
await setState({ loginVerificationRequestedAt: payload.loginVerificationRequestedAt });
|
||||||
}
|
}
|
||||||
@@ -11509,6 +11518,7 @@ const step8Executor = self.MultiPageBackgroundStep8?.createStep8Executor({
|
|||||||
LUCKMAIL_PROVIDER,
|
LUCKMAIL_PROVIDER,
|
||||||
resolveVerificationStep: verificationFlowHelpers.resolveVerificationStep,
|
resolveVerificationStep: verificationFlowHelpers.resolveVerificationStep,
|
||||||
resolveSignupEmailForFlow,
|
resolveSignupEmailForFlow,
|
||||||
|
persistRegistrationEmailState,
|
||||||
phoneVerificationHelpers,
|
phoneVerificationHelpers,
|
||||||
rerunStep7ForStep8Recovery: (...args) => rerunStep7ForStep8Recovery(...args),
|
rerunStep7ForStep8Recovery: (...args) => rerunStep7ForStep8Recovery(...args),
|
||||||
resolveSignupMethod,
|
resolveSignupMethod,
|
||||||
|
|||||||
@@ -574,6 +574,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 7:
|
||||||
|
await syncStepAccountIdentityFromPayload(payload);
|
||||||
|
if (payload.loginVerificationRequestedAt) {
|
||||||
|
await setState({ loginVerificationRequestedAt: payload.loginVerificationRequestedAt });
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 8:
|
case 8:
|
||||||
await setState({
|
await setState({
|
||||||
...(payload.phoneVerification || payload.loginPhoneVerification ? {
|
...(payload.phoneVerification || payload.loginPhoneVerification ? {
|
||||||
|
|||||||
@@ -182,21 +182,28 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const displayedEmail = normalizeStep8VerificationTargetEmail(result?.displayedEmail || resolvedEmail);
|
const displayedEmail = normalizeStep8VerificationTargetEmail(result?.displayedEmail || resolvedEmail);
|
||||||
|
let persistedState = latestState;
|
||||||
if (typeof persistRegistrationEmailState === 'function') {
|
if (typeof persistRegistrationEmailState === 'function') {
|
||||||
await persistRegistrationEmailState(latestState, resolvedEmail, {
|
await persistRegistrationEmailState(latestState, resolvedEmail, {
|
||||||
source: 'step8_add_email',
|
source: 'step8_add_email',
|
||||||
preserveAccountIdentity: true,
|
preserveAccountIdentity: true,
|
||||||
});
|
});
|
||||||
|
persistedState = typeof getState === 'function' ? await getState() : latestState;
|
||||||
} else {
|
} else {
|
||||||
await setState({
|
await setState({
|
||||||
email: resolvedEmail,
|
email: resolvedEmail,
|
||||||
step8VerificationTargetEmail: displayedEmail,
|
step8VerificationTargetEmail: displayedEmail,
|
||||||
});
|
});
|
||||||
|
persistedState = {
|
||||||
|
...latestState,
|
||||||
|
email: resolvedEmail,
|
||||||
|
step8VerificationTargetEmail: displayedEmail,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state: {
|
state: {
|
||||||
...latestState,
|
...persistedState,
|
||||||
email: resolvedEmail,
|
email: resolvedEmail,
|
||||||
step8VerificationTargetEmail: displayedEmail,
|
step8VerificationTargetEmail: displayedEmail,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -263,6 +263,13 @@
|
|||||||
const completionPayload = {
|
const completionPayload = {
|
||||||
loginVerificationRequestedAt: result.loginVerificationRequestedAt || null,
|
loginVerificationRequestedAt: result.loginVerificationRequestedAt || null,
|
||||||
};
|
};
|
||||||
|
if (currentIdentifierType === 'phone') {
|
||||||
|
completionPayload.accountIdentifierType = 'phone';
|
||||||
|
completionPayload.accountIdentifier = currentPhoneNumber;
|
||||||
|
completionPayload.signupPhoneNumber = currentPhoneNumber;
|
||||||
|
completionPayload.signupPhoneCompletedActivation = currentState?.signupPhoneCompletedActivation || null;
|
||||||
|
completionPayload.signupPhoneActivation = currentState?.signupPhoneActivation || null;
|
||||||
|
}
|
||||||
if (Object.prototype.hasOwnProperty.call(result || {}, 'skipLoginVerificationStep')) {
|
if (Object.prototype.hasOwnProperty.call(result || {}, 'skipLoginVerificationStep')) {
|
||||||
completionPayload.skipLoginVerificationStep = Boolean(result.skipLoginVerificationStep);
|
completionPayload.skipLoginVerificationStep = Boolean(result.skipLoginVerificationStep);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -244,6 +244,31 @@ test('message router preserves phone signup identity when step payload only repo
|
|||||||
assert.ok(events.stateUpdates.some((updates) => updates.signupVerificationRequestedAt === 123456));
|
assert.ok(events.stateUpdates.some((updates) => updates.signupVerificationRequestedAt === 123456));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('message router persists phone signup identity from step 7 completion payload', async () => {
|
||||||
|
const completedActivation = {
|
||||||
|
activationId: 'signup-done',
|
||||||
|
phoneNumber: '+5511917097811',
|
||||||
|
};
|
||||||
|
const { router, events } = createRouter({
|
||||||
|
state: { stepStatuses: { 7: 'completed', 8: 'pending' } },
|
||||||
|
});
|
||||||
|
|
||||||
|
await router.handleStepData(7, {
|
||||||
|
loginVerificationRequestedAt: 123456,
|
||||||
|
accountIdentifierType: 'phone',
|
||||||
|
accountIdentifier: '+5511917097811',
|
||||||
|
signupPhoneNumber: '+5511917097811',
|
||||||
|
signupPhoneActivation: null,
|
||||||
|
signupPhoneCompletedActivation: completedActivation,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(events.signupPhoneSilentStates, ['+5511917097811']);
|
||||||
|
assert.ok(events.stateUpdates.some((updates) => updates.signupPhoneActivation === null));
|
||||||
|
assert.ok(events.stateUpdates.some((updates) => updates.signupPhoneCompletedActivation === completedActivation));
|
||||||
|
assert.ok(events.stateUpdates.some((updates) => updates.loginVerificationRequestedAt === 123456));
|
||||||
|
assert.deepStrictEqual(events.emailStates, []);
|
||||||
|
});
|
||||||
|
|
||||||
test('message router does not overwrite a completed step 3 when step 2 is replayed', async () => {
|
test('message router does not overwrite a completed step 3 when step 2 is replayed', async () => {
|
||||||
const { router, events } = createRouter({
|
const { router, events } = createRouter({
|
||||||
state: { stepStatuses: { 3: 'completed' } },
|
state: { stepStatuses: { 3: 'completed' } },
|
||||||
|
|||||||
@@ -463,12 +463,15 @@ test('step 7 forwards phone login identity payload when account identifier is ph
|
|||||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||||
|
|
||||||
const events = {
|
const events = {
|
||||||
|
completions: [],
|
||||||
payloads: [],
|
payloads: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const executor = api.createStep7Executor({
|
const executor = api.createStep7Executor({
|
||||||
addLog: async () => {},
|
addLog: async () => {},
|
||||||
completeStepFromBackground: async () => {},
|
completeStepFromBackground: async (step, payload) => {
|
||||||
|
events.completions.push({ step, payload });
|
||||||
|
},
|
||||||
getErrorMessage: (error) => error?.message || String(error || ''),
|
getErrorMessage: (error) => error?.message || String(error || ''),
|
||||||
getLoginAuthStateLabel: (state) => state || 'unknown',
|
getLoginAuthStateLabel: (state) => state || 'unknown',
|
||||||
getState: async () => ({
|
getState: async () => ({
|
||||||
@@ -524,6 +527,24 @@ test('step 7 forwards phone login identity payload when account identifier is ph
|
|||||||
visibleStep: 7,
|
visibleStep: 7,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
assert.deepStrictEqual(events.completions, [
|
||||||
|
{
|
||||||
|
step: 7,
|
||||||
|
payload: {
|
||||||
|
loginVerificationRequestedAt: 123456,
|
||||||
|
accountIdentifierType: 'phone',
|
||||||
|
accountIdentifier: '66959916439',
|
||||||
|
signupPhoneNumber: '66959916439',
|
||||||
|
signupPhoneCompletedActivation: {
|
||||||
|
activationId: 'signup-done',
|
||||||
|
phoneNumber: '66959916439',
|
||||||
|
countryId: 52,
|
||||||
|
countryLabel: 'Thailand',
|
||||||
|
},
|
||||||
|
signupPhoneActivation: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('step 7 keeps Plus email login even when phone sms runtime exists', async () => {
|
test('step 7 keeps Plus email login even when phone sms runtime exists', async () => {
|
||||||
@@ -737,6 +758,11 @@ test('step 7 can start from a manually filled signup phone without completed ste
|
|||||||
step: 7,
|
step: 7,
|
||||||
payload: {
|
payload: {
|
||||||
loginVerificationRequestedAt: 987654,
|
loginVerificationRequestedAt: 987654,
|
||||||
|
accountIdentifierType: 'phone',
|
||||||
|
accountIdentifier: '+447780579093',
|
||||||
|
signupPhoneNumber: '+447780579093',
|
||||||
|
signupPhoneCompletedActivation: null,
|
||||||
|
signupPhoneActivation: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -472,6 +472,106 @@ test('step 8 reruns step 7 with preserved phone login identity after add-email v
|
|||||||
assert.equal(calls.rerunStates[0].signupPhoneNumber, '+447780579093');
|
assert.equal(calls.rerunStates[0].signupPhoneNumber, '+447780579093');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('step 8 add-email rereads persisted phone identity before rerunning step 7', async () => {
|
||||||
|
const calls = {
|
||||||
|
resolveCalls: 0,
|
||||||
|
rerunStates: [],
|
||||||
|
};
|
||||||
|
let runtimeState = {
|
||||||
|
visibleStep: 8,
|
||||||
|
email: '',
|
||||||
|
password: 'secret',
|
||||||
|
oauthUrl: 'https://oauth.example/latest',
|
||||||
|
accountIdentifierType: 'email',
|
||||||
|
accountIdentifier: 'stale@example.com',
|
||||||
|
signupMethod: 'phone',
|
||||||
|
resolvedSignupMethod: 'phone',
|
||||||
|
phoneVerificationEnabled: true,
|
||||||
|
signupPhoneNumber: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const executor = api.createStep8Executor({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
update: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||||
|
confirmCustomVerificationStepBypass: async () => {},
|
||||||
|
ensureStep8VerificationPageReady: async () => ({ state: 'add_email_page', url: 'https://auth.openai.com/add-email' }),
|
||||||
|
getOAuthFlowRemainingMs: async () => 8000,
|
||||||
|
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => Math.min(defaultTimeoutMs, 8000),
|
||||||
|
getMailConfig: () => ({
|
||||||
|
provider: 'qq',
|
||||||
|
label: 'QQ 邮箱',
|
||||||
|
source: 'mail-qq',
|
||||||
|
url: 'https://mail.qq.com',
|
||||||
|
navigateOnReuse: false,
|
||||||
|
}),
|
||||||
|
getState: async () => ({ ...runtimeState }),
|
||||||
|
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
|
||||||
|
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||||
|
isTabAlive: async () => true,
|
||||||
|
isVerificationMailPollingError: () => false,
|
||||||
|
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||||
|
persistRegistrationEmailState: async (_state, email, options) => {
|
||||||
|
assert.equal(email, 'new.user@example.com');
|
||||||
|
assert.equal(options.preserveAccountIdentity, true);
|
||||||
|
runtimeState = {
|
||||||
|
...runtimeState,
|
||||||
|
email,
|
||||||
|
accountIdentifierType: 'phone',
|
||||||
|
accountIdentifier: '+447780579093',
|
||||||
|
signupPhoneNumber: '+447780579093',
|
||||||
|
signupPhoneCompletedActivation: {
|
||||||
|
activationId: 'signup-done',
|
||||||
|
phoneNumber: '+447780579093',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
resolveSignupEmailForFlow: async (_state, options = {}) => {
|
||||||
|
assert.equal(options.preserveAccountIdentity, true);
|
||||||
|
return 'new.user@example.com';
|
||||||
|
},
|
||||||
|
resolveVerificationStep: async (_step, state) => {
|
||||||
|
calls.resolveCalls += 1;
|
||||||
|
assert.equal(state.accountIdentifierType, 'phone');
|
||||||
|
assert.equal(state.signupPhoneNumber, '+447780579093');
|
||||||
|
throw new Error('STEP8_RESTART_STEP7::step 8 verification page fell into login timeout retry state');
|
||||||
|
},
|
||||||
|
rerunStep7ForStep8Recovery: async () => {
|
||||||
|
calls.rerunStates.push({ ...runtimeState });
|
||||||
|
},
|
||||||
|
reuseOrCreateTab: async () => {},
|
||||||
|
sendToContentScriptResilient: async () => ({
|
||||||
|
submitted: true,
|
||||||
|
displayedEmail: 'new.user@example.com',
|
||||||
|
url: 'https://auth.openai.com/email-verification',
|
||||||
|
}),
|
||||||
|
setState: async (payload) => {
|
||||||
|
runtimeState = {
|
||||||
|
...runtimeState,
|
||||||
|
...payload,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
shouldUseCustomRegistrationEmail: () => false,
|
||||||
|
STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS: 25000,
|
||||||
|
STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS: 2,
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() => executor.executeStep8({ ...runtimeState }),
|
||||||
|
/STEP8_RESTART_STEP7::/
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(calls.resolveCalls, 2);
|
||||||
|
assert.equal(calls.rerunStates.length, 1);
|
||||||
|
assert.equal(calls.rerunStates[0].accountIdentifierType, 'phone');
|
||||||
|
assert.equal(calls.rerunStates[0].signupPhoneNumber, '+447780579093');
|
||||||
|
});
|
||||||
|
|
||||||
test('step 8 email_in_use recovery preserves the previous registration baseline', async () => {
|
test('step 8 email_in_use recovery preserves the previous registration baseline', async () => {
|
||||||
const calls = {
|
const calls = {
|
||||||
contentCalls: 0,
|
contentCalls: 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user