Skip to content

Fix potential race condition bug in pwless login#2758

Merged
alexvuong merged 35 commits intodevelopfrom
fix-pwless-login-contact-checkout
Jul 14, 2025
Merged

Fix potential race condition bug in pwless login#2758
alexvuong merged 35 commits intodevelopfrom
fix-pwless-login-contact-checkout

Conversation

@alexvuong
Copy link
Contributor

@alexvuong alexvuong commented Jul 8, 2025

Description

There is a potential bug in pwless logic in check out contract info form when users click "Secure Link" to submit pwless flow.

const submitForm = async (data) => {
        setError(null)
        if (isPasswordlessLoginClicked) {
            handlePasswordlessLogin(data.email)
            setIsPasswordlessLoginClicked(false)
            return
        }
....
}

We are trying to setState during submission and expecting the state to update immediately. This is a wrong and bug-prone because React setSate is asynchronous. See explanation here.
This will result in a race condition that we are not anticipating in very slow devices
This bug shows up during unit tests, but we are cheating by clicking the "Secure Link" button twice which forces a a re-rendering the component where it is not supposed in reality. The users on the browser never has to click twice on "Secure Link" to see the modal pop up.

Why It Works in Browser (Single Click)

  1. Natural Timing Delays
    In the browser, there are natural micro-delays between:
    User click → Event handler execution
    State update → Form submission
    DOM updates → Re-rendering
    These small delays give React time to process the state update before the form submission logic runs.
  2. Event Loop Differences
    The browser's event loop processes things differently than the test environment:
    Browser: Asynchronous, with natural pauses for repainting, user interaction, etc.
    Test: Synchronous, everything happens in the same tick
  3. React's Batching Behavior
    React batches state updates differently in:
    Browser: More time between batches, allowing updates to complete
    Test: Immediate batching, exposing race conditions

Types of Changes

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Documentation update
  • Breaking change (could cause existing functionality to not work as expected)
  • Other changes (non-breaking changes that does not fit any of the above)

Breaking changes include:

  • Removing a public function or component or prop
  • Adding a required argument to a function
  • Changing the data type of a function parameter or return value
  • Adding a new peer dependency to package.json

Changes

  • (change1)

How to Test-Drive This PR

  • Check out code
  • enabled pwless login
  • run unit tests and make sure all tests are passed
  • Enabled pwless and set up private slas to test the feature

Checklists

General

  • Changes are covered by test cases
  • CHANGELOG.md updated with a short description of changes (not required for documentation updates)

Accessibility Compliance

You must check off all items in one of the follow two lists:

  • There are no changes to UI

or...

Localization

  • Changes include a UI text update in the Retail React App (which requires translation)

@alexvuong alexvuong requested a review from a team as a code owner July 8, 2025 22:19
@cc-prodsec
Copy link
Collaborator

cc-prodsec commented Jul 8, 2025

🎉 Snyk checks have passed. No issues have been found so far.

security/snyk check is complete. No issues have been found. (View Details)

license/snyk check is complete. No issues have been found. (View Details)

variant="outline"
borderColor="gray.500"
type="submit"
type="button"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We know that pwless does not require the same flow as other flows, we can safely use type=button to avoid it go through submission and handle the flow right in the onPasswordlessLoginClick handler

pathname: '/checkout'
const mockLocation = jest.spyOn(window, 'location', 'get').mockReturnValue({
pathname: '/checkout',
origin: 'https://webhook.site'
Copy link
Contributor Author

@alexvuong alexvuong Jul 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix error during tests

 console.error
    TypeError: Invalid URL: undefined//mobify/proxy/api
        ....

@hajinsuha1
Copy link
Collaborator

Thanks @alexvuong for looking into this and finding a fix!
There are 2 other places that use the same pattern for the passwordless login that likely have the same issue:

Comment on lines +169 to +193
// since the pwless is no longer to through form submission flow,
// we need to manual validate the email value here
const email = form.getValues().email
if (!email) {
form.setError('email', {
type: 'manual',
message: formatMessage({
defaultMessage: 'Please enter your email address.',
id: 'contact_info.error.email_required'
})
})
return
}

const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(email)) {
form.setError('email', {
type: 'manual',
message: formatMessage({
defaultMessage: 'Please enter a valid email address.',
id: 'contact_info.error.email_invalid'
})
})
return
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be the first time we introduce a regex to validate email in the app. The regex doesn't cover all valid email scenarios (e.g., internationalized domains, plus signs, etc.).

Should we stick with react-hook-form's native validation like we do in other places?

Example:

const validateEmail = async () => {
    const isValid = await form.trigger('email')
    return isValid
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good call, I will work on that

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is fixed

@alexvuong
Copy link
Contributor Author

@hajinsuha1 @adamraya Thanks for reviewing. I've addressed your comments. I've fixed the same flow for login and use-auth-modal.

# Conflicts:
#	packages/template-retail-react-app/CHANGELOG.md
adamraya
adamraya previously approved these changes Jul 10, 2025
@alexvuong alexvuong changed the title Fix pwless login bug Fix potential race condition bug in pwless login Jul 11, 2025
@alexvuong
Copy link
Contributor Author

@hajinsuha1 Good catch, I've changed the code to make pwless to be aware on Enter key. Please have another review when you have time
@adamraya @yunakim714 Please have another review when you have time as well

I've deployed the bundle to this env if you want to test it on MRT https://runtime.commercecloud.com/salesforce-systems/scaffold-pwa/test-env-3

isSocialEnabled = false,
idps = [],
setLoginType
setLoginType = noop
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't need this prop in the logic but we can't remove it now since it will be a breaking change.

}
//TODO: potentially remove this prop in the next major release since
// we don't need to use this props anymore
handlePasswordlessLoginClick={noop}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't need this prop in the logic but we can't remove it now since it will be a breaking change.

Copy link
Collaborator

@hajinsuha1 hajinsuha1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking the time to fix this! Retested and everything looks good.

Copy link
Contributor

@adamraya adamraya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested the changes on my machine and LGTM

@alexvuong alexvuong enabled auto-merge (squash) July 14, 2025 23:01
@alexvuong alexvuong merged commit 271a1f1 into develop Jul 14, 2025
36 checks passed
@alexvuong alexvuong deleted the fix-pwless-login-contact-checkout branch July 16, 2025 22:13
kumaravinashcommercecloud pushed a commit that referenced this pull request Aug 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants