fix(auth): preserve phone identity when step payload carries bound email
This commit is contained in:
+32
-3
@@ -8924,6 +8924,34 @@ async function handleStepData(step, payload) {
|
|||||||
return messageRouter.handleStepData(step, payload);
|
return messageRouter.handleStepData(step, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function shouldPreservePhoneIdentityForStepEmailPayload(state = {}, stepPayload = {}) {
|
||||||
|
if (String(stepPayload.accountIdentifierType || '').trim().toLowerCase() === 'email') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Boolean(
|
||||||
|
String(state.signupPhoneNumber || '').trim()
|
||||||
|
|| (String(state.accountIdentifierType || '').trim().toLowerCase() === 'phone'
|
||||||
|
&& String(state.accountIdentifier || '').trim())
|
||||||
|
|| state.signupPhoneActivation
|
||||||
|
|| state.signupPhoneCompletedActivation
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function persistStepEmailPayload(email, stepPayload = {}, source = 'step_identity') {
|
||||||
|
if (!email) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const currentState = await getState();
|
||||||
|
if (shouldPreservePhoneIdentityForStepEmailPayload(currentState, stepPayload)) {
|
||||||
|
await persistRegistrationEmailState(currentState, email, {
|
||||||
|
source,
|
||||||
|
preserveAccountIdentity: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await setEmailState(email);
|
||||||
|
}
|
||||||
|
|
||||||
switch (step) {
|
switch (step) {
|
||||||
case 1: {
|
case 1: {
|
||||||
const updates = {};
|
const updates = {};
|
||||||
@@ -8949,8 +8977,8 @@ async function handleStepData(step, payload) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 2:
|
case 2:
|
||||||
if (payload.email) await setEmailState(payload.email);
|
await persistStepEmailPayload(payload.email, payload, 'step2_identity');
|
||||||
if (payload.accountIdentifierType || payload.accountIdentifier || payload.signupPhoneNumber || payload.signupPhoneActivation) {
|
if (!payload.email && (payload.accountIdentifierType || payload.accountIdentifier || payload.signupPhoneNumber || payload.signupPhoneActivation)) {
|
||||||
await setState({
|
await setState({
|
||||||
accountIdentifierType: payload.accountIdentifierType || null,
|
accountIdentifierType: payload.accountIdentifierType || null,
|
||||||
accountIdentifier: String(payload.accountIdentifier || '').trim(),
|
accountIdentifier: String(payload.accountIdentifier || '').trim(),
|
||||||
@@ -8969,7 +8997,7 @@ async function handleStepData(step, payload) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
if (payload.email) await setEmailState(payload.email);
|
await persistStepEmailPayload(payload.email, payload, 'step3_identity');
|
||||||
if (payload.signupVerificationRequestedAt) {
|
if (payload.signupVerificationRequestedAt) {
|
||||||
await setState({ signupVerificationRequestedAt: payload.signupVerificationRequestedAt });
|
await setState({ signupVerificationRequestedAt: payload.signupVerificationRequestedAt });
|
||||||
}
|
}
|
||||||
@@ -11724,6 +11752,7 @@ const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter
|
|||||||
setContributionMode,
|
setContributionMode,
|
||||||
setEmailState,
|
setEmailState,
|
||||||
setEmailStateSilently,
|
setEmailStateSilently,
|
||||||
|
persistRegistrationEmailState,
|
||||||
setFreeReusablePhoneActivation,
|
setFreeReusablePhoneActivation,
|
||||||
setSignupPhoneState,
|
setSignupPhoneState,
|
||||||
setSignupPhoneStateSilently,
|
setSignupPhoneStateSilently,
|
||||||
|
|||||||
@@ -104,6 +104,7 @@
|
|||||||
setContributionMode,
|
setContributionMode,
|
||||||
setEmailState,
|
setEmailState,
|
||||||
setEmailStateSilently,
|
setEmailStateSilently,
|
||||||
|
persistRegistrationEmailState,
|
||||||
setFreeReusablePhoneActivation,
|
setFreeReusablePhoneActivation,
|
||||||
setSignupPhoneState,
|
setSignupPhoneState,
|
||||||
setSignupPhoneStateSilently,
|
setSignupPhoneStateSilently,
|
||||||
@@ -210,6 +211,42 @@
|
|||||||
: '';
|
: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasPhoneSignupIdentity(state = {}) {
|
||||||
|
const identifierType = String(state?.accountIdentifierType || '').trim().toLowerCase();
|
||||||
|
return Boolean(
|
||||||
|
String(state?.signupPhoneNumber || '').trim()
|
||||||
|
|| (identifierType === 'phone' && String(state?.accountIdentifier || '').trim())
|
||||||
|
|| state?.signupPhoneActivation
|
||||||
|
|| state?.signupPhoneCompletedActivation
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldPreservePhoneIdentityForEmailPayload(payload = {}, state = {}) {
|
||||||
|
const identifierType = String(payload?.accountIdentifierType || '').trim().toLowerCase();
|
||||||
|
if (identifierType === 'email') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return hasPhoneSignupIdentity(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function persistEmailIdentityFromStepPayload(email, payload = {}, source = 'step_payload') {
|
||||||
|
if (!email) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const state = await getState();
|
||||||
|
const preserveAccountIdentity = shouldPreservePhoneIdentityForEmailPayload(payload, state);
|
||||||
|
if (preserveAccountIdentity && typeof persistRegistrationEmailState === 'function') {
|
||||||
|
await persistRegistrationEmailState(state, email, {
|
||||||
|
source,
|
||||||
|
preserveAccountIdentity: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await setEmailState(email, preserveAccountIdentity
|
||||||
|
? { source, preserveAccountIdentity: true }
|
||||||
|
: { source });
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeAutomationWindowId(value) {
|
function normalizeAutomationWindowId(value) {
|
||||||
if (value === null || value === undefined || value === '') {
|
if (value === null || value === undefined || value === '') {
|
||||||
return null;
|
return null;
|
||||||
@@ -262,7 +299,10 @@
|
|||||||
const email = resolveEmailIdentityPayload(payload);
|
const email = resolveEmailIdentityPayload(payload);
|
||||||
if (identifierType === 'email' || email) {
|
if (identifierType === 'email' || email) {
|
||||||
if (email) {
|
if (email) {
|
||||||
await setEmailState(email);
|
await persistEmailIdentityFromStepPayload(email, payload, 'step_identity');
|
||||||
|
}
|
||||||
|
if (email) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
const updates = {
|
const updates = {
|
||||||
phoneNumber: '',
|
phoneNumber: '',
|
||||||
|
|||||||
@@ -83,6 +83,65 @@ const defaultStepDefinitions = {
|
|||||||
10: { key: 'platform-verify' },
|
10: { key: 'platform-verify' },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const PHONE_IDENTITY_STATE_KEYS = [
|
||||||
|
'phoneNumber',
|
||||||
|
'signupPhoneNumber',
|
||||||
|
'signupPhoneActivation',
|
||||||
|
'signupPhoneCompletedActivation',
|
||||||
|
'signupPhoneVerificationRequestedAt',
|
||||||
|
'signupPhoneVerificationPurpose',
|
||||||
|
'accountIdentifierType',
|
||||||
|
'accountIdentifier',
|
||||||
|
];
|
||||||
|
|
||||||
|
function createDownstreamResetHarness(stepKey = '') {
|
||||||
|
return new Function(`
|
||||||
|
function getStepExecutionKeyForState() {
|
||||||
|
return ${JSON.stringify(stepKey)};
|
||||||
|
}
|
||||||
|
${extractFunction('getDownstreamStateResets')}
|
||||||
|
return { getDownstreamStateResets };
|
||||||
|
`)();
|
||||||
|
}
|
||||||
|
|
||||||
|
test('downstream restarts after account creation preserve phone signup identity fields', () => {
|
||||||
|
const numericResetHarness = createDownstreamResetHarness('');
|
||||||
|
const stepKeyResetHarnesses = [
|
||||||
|
createDownstreamResetHarness('oauth-login'),
|
||||||
|
createDownstreamResetHarness('fetch-login-code'),
|
||||||
|
createDownstreamResetHarness('confirm-oauth'),
|
||||||
|
];
|
||||||
|
const phoneState = {
|
||||||
|
accountIdentifierType: 'phone',
|
||||||
|
accountIdentifier: '+447780579093',
|
||||||
|
signupPhoneNumber: '+447780579093',
|
||||||
|
signupPhoneActivation: { activationId: 'active', phoneNumber: '+447780579093' },
|
||||||
|
signupPhoneCompletedActivation: { activationId: 'done', phoneNumber: '+447780579093' },
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const step of [5, 6, 7, 8, 9]) {
|
||||||
|
const resets = numericResetHarness.getDownstreamStateResets(step, phoneState);
|
||||||
|
for (const key of PHONE_IDENTITY_STATE_KEYS) {
|
||||||
|
assert.equal(
|
||||||
|
Object.prototype.hasOwnProperty.call(resets, key),
|
||||||
|
false,
|
||||||
|
`step ${step} reset must not clear ${key}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const harness of stepKeyResetHarnesses) {
|
||||||
|
const resets = harness.getDownstreamStateResets(10, phoneState);
|
||||||
|
for (const key of PHONE_IDENTITY_STATE_KEYS) {
|
||||||
|
assert.equal(
|
||||||
|
Object.prototype.hasOwnProperty.call(resets, key),
|
||||||
|
false,
|
||||||
|
`${key} must not be cleared by step-key reset`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function createHarness(options = {}) {
|
function createHarness(options = {}) {
|
||||||
const {
|
const {
|
||||||
startStep = 7,
|
startStep = 7,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ function createRouter(overrides = {}) {
|
|||||||
broadcasts: [],
|
broadcasts: [],
|
||||||
balanceRefreshes: [],
|
balanceRefreshes: [],
|
||||||
emailStates: [],
|
emailStates: [],
|
||||||
|
persistedRegistrationEmails: [],
|
||||||
signupPhoneStates: [],
|
signupPhoneStates: [],
|
||||||
signupPhoneSilentStates: [],
|
signupPhoneSilentStates: [],
|
||||||
finalizePayloads: [],
|
finalizePayloads: [],
|
||||||
@@ -123,6 +124,9 @@ function createRouter(overrides = {}) {
|
|||||||
events.emailStates.push(email);
|
events.emailStates.push(email);
|
||||||
},
|
},
|
||||||
setEmailStateSilently: async () => {},
|
setEmailStateSilently: async () => {},
|
||||||
|
persistRegistrationEmailState: async (state, email, options) => {
|
||||||
|
events.persistedRegistrationEmails.push({ state, email, options });
|
||||||
|
},
|
||||||
setSignupPhoneState: async (phoneNumber) => {
|
setSignupPhoneState: async (phoneNumber) => {
|
||||||
events.signupPhoneStates.push(phoneNumber);
|
events.signupPhoneStates.push(phoneNumber);
|
||||||
},
|
},
|
||||||
@@ -206,14 +210,38 @@ test('message router clears stale signup phone runtime when step 2 resolves emai
|
|||||||
});
|
});
|
||||||
|
|
||||||
assert.deepStrictEqual(events.emailStates, ['user@example.com']);
|
assert.deepStrictEqual(events.emailStates, ['user@example.com']);
|
||||||
assert.deepStrictEqual(events.signupPhoneSilentStates, [null]);
|
assert.deepStrictEqual(events.signupPhoneSilentStates, []);
|
||||||
assert.ok(events.stateUpdates.some((updates) => (
|
assert.deepStrictEqual(events.stateUpdates, []);
|
||||||
updates.accountIdentifierType === 'email'
|
});
|
||||||
&& updates.accountIdentifier === 'user@example.com'
|
|
||||||
&& updates.signupPhoneNumber === ''
|
test('message router preserves phone signup identity when step payload only reports registration email', async () => {
|
||||||
&& updates.signupPhoneActivation === null
|
const { router, events } = createRouter({
|
||||||
&& updates.signupPhoneCompletedActivation === null
|
state: {
|
||||||
)));
|
stepStatuses: { 3: 'pending' },
|
||||||
|
accountIdentifierType: 'phone',
|
||||||
|
accountIdentifier: '+66959916439',
|
||||||
|
signupPhoneNumber: '+66959916439',
|
||||||
|
signupPhoneActivation: { activationId: 'active', phoneNumber: '+66959916439' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await router.handleStepData(3, {
|
||||||
|
email: 'bound@example.com',
|
||||||
|
signupVerificationRequestedAt: 123456,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(events.emailStates, []);
|
||||||
|
assert.equal(events.persistedRegistrationEmails.length, 1);
|
||||||
|
assert.equal(events.persistedRegistrationEmails[0].email, 'bound@example.com');
|
||||||
|
assert.deepStrictEqual(events.persistedRegistrationEmails[0].options, {
|
||||||
|
source: 'step_identity',
|
||||||
|
preserveAccountIdentity: true,
|
||||||
|
});
|
||||||
|
assert.equal(events.persistedRegistrationEmails[0].state.signupPhoneNumber, '+66959916439');
|
||||||
|
assert.equal(events.persistedRegistrationEmails[0].state.accountIdentifierType, 'phone');
|
||||||
|
assert.equal(events.signupPhoneSilentStates.length, 0);
|
||||||
|
assert.ok(!events.stateUpdates.some((updates) => updates.signupPhoneNumber === ''));
|
||||||
|
assert.ok(events.stateUpdates.some((updates) => updates.signupVerificationRequestedAt === 123456));
|
||||||
});
|
});
|
||||||
|
|
||||||
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 () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user