Skip to content

@W-24382352 - Send login_id + channel_id on passwordless/token and user_id + channel_id on password/action - #4075

Open
yunakim714 wants to merge 14 commits into
developfrom
W-24382352-send-login-channel-id
Open

yunakim714 wants to merge 14 commits into
developfrom
W-24382352-send-login-channel-id

Conversation

@yunakim714

@yunakim714 yunakim714 commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Require login_id and channel_id on passwordless OTP token exchange so SLAS can bind the token to the shopper identifier and site. Forward both through commerce-sdk-react, pass them from all retail loginPasswordless call sites (including magic-link landing via login_id on the URL), and map related SLAS errors to existing UX.

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

  • commerce-sdk-react: Make login_id and channel_id required on getPasswordLessAccessToken / LoginPasswordlessUser; forward them to the isomorphic helper. Drop the stale @ts-ignore on resetPassword user_id.
  • retail app: Pass shopper email + site.id at login, auth modal, and one-click contact/registration OTP call sites; include login_id on passwordless magic-link URLs.
  • Errors: Map 400 missing/mismatched identifier and passwordless rate limits (429) to existing OTP/login messages; map password/action rate limits to the invalid-token reset-password UX.
  • Changelogs / tests updated for both packages.

How to Test-Drive This PR

Automated

  • Run all commerce-sdk-react and template-retail-react-app unit tests against the locally built commerce-sdk-isomorphic-6.0.0 version:

template-retail-react-app
Screenshot 2026-11-03 at 3 43 12 PM

commerce-sdk-react
Screenshot 2026-11-03 at 3 47 38 PM

Manual — passwordless login

  • Login page OTP: enter email → receive/enter code → login succeeds; network call includes login_id + channel_id
  • Auth modal: same OTP flow
  • Magic link: click email link → lands with ?token=…&login_id=… → auto-login
  • Wrong/mismatched login_id in URL → invalid-token style error (not a generic API message)
  • Edit email in the form after authorize, then submit OTP → still uses authorize-time email (one-click contact especially)

Manual — one-click checkout

  • Contact info: registered email → OTP → verify; basket merges / continues checkout
  • Guest registration checkbox: OTP with register_customer; creates account
  • Missing authorize email edge case → “email is missing… Resend Code” (not generic API error)

Manual — rate limits / errors

  • Passwordless OTP: force 429 → too-many-login-attempts message
  • Reset-password landing (/password/action): force 429 → too-many-password-reset-attempts (wait message), not “invalid token / request a new link”
  • Invalid/expired reset token → still invalid-token UX

Manual — reset password (non-rate-limit)

  • Valid reset magic link → set password → redirect to login
  • Bad token → invalid token error

Sanity

  • Guest checkout (no passwordless) still works
  • Password login still works
  • Confirm isomorphic / CSR versions used for local test match the PR (commerce-sdk-isomorphic@6 + CSR 6.0.0-dev if that’s what you’re validating against)

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:

  • Changes were tested with a Screen Reader (iOS VoiceOver or Android Talkback) and had no issues
  • Changes comply with WCAG 2.0 levels A and AA
  • Changes to common UI patterns and interactions comply with WAI-ARIA best practices

Localization

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

@git2gus

git2gus Bot commented Oct 23, 2026

Copy link
Copy Markdown

Git2Gus App is installed but the .git2gus/config.json doesn't have right values. You should add the required configuration.

@cc-prodsec

cc-prodsec commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

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

Status Scan Engine Critical High Medium Low Total (0)
✅ Open Source Security 0 0 0 0 0 issues
✅ Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@yunakim714
yunakim714 marked this pull request as ready for review October 23, 2026 18:57
@yunakim714
yunakim714 requested review from a team as code owners October 23, 2026 18:57
INVALID_TOKEN_ERROR.test(errorMessage) ||
MISSING_OR_MISMATCHED_IDENTIFIER_ERROR.test(String(errorMessage))
) {
return INVALID_TOKEN_ERROR_MESSAGE

@yunakim714 yunakim714 Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Should the new MISSING_OR_MISMATCHED_IDENTIFIER_ERROR display a different error message?

@jeremy-jung1 jeremy-jung1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for picking this up — binding the passwordless token to login_id + channel_id is the right call. Inline comments have the details; summary + the bits that don't anchor to a diff line:

🔴 Blocking

  1. login_id / channel_id don't reach SLAS yet — see inline on auth/index.ts. Needs commerce-sdk-isomorphic@6.0.0 (from commerce-sdk-isomorphic#304) plus a camelCase mapping here. The bump has to land in both packages/commerce-sdk-react/package.json and packages/template-retail-react-app/package.json (both pin 5.7.0), plus lockfiles.
  2. 429 handling can't work and leaves the shopper with no error — see inline on auth-utils.js.

🟠 Should fix

  1. Breaking change → [Breaking] + major version (inline on CHANGELOG)
  2. Match real SLAS error strings (inline)
  3. Reset-password rate limit shown as invalid token (inline)
  4. Shopper email in the magic-link URL (inline on ssr.js)
  5. Rollout / back-compat note — these magic-link paths will send login_id: '': links emailed before this deploys, existing generated projects (they own their copy of ssr.js), and merchants with a custom callback/email service. SLAS ignores a blank login_id today so nothing breaks yet, but it will once SLAS requires it — please call out the ordering and what merchants need to update.

🟡 Nits

  • Inline: double decode, contact-info login_id source, registration undefined email, stray blank lines in two test files.
  • "How to Test-Drive This PR" is still the (step1) placeholder.

✅ Nice

  • All four LoginPasswordlessUser call sites updated, and the login_id lines up with the userid sent to authorizePasswordless.
  • Dropping the stale @ts-ignore on resetPassword user_id is correct; reset already sends user_id.
  • Pulling out getResetPasswordActionErrorMessage is a nice cleanup.

@jeremy-jung1 jeremy-jung1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the updates! Most of the earlier points are addressed. The camelCase loginId/channelId mapping, the status-based 429 handling, the exact SLAS error strings, the reset-password 429 message, the authorize-time email in one-click checkout, the wire-body test, and the notify-type fix with its ssr.test.js coverage all look good. A few things remain. File-specific ones are inline.

1. CI can't run until isomorphic 6.0.0 is published, but you can reproduce it locally

Every job fails at npm ci (notarget No matching version found for commerce-sdk-isomorphic@6.0.0), so none of the tests have run in CI. You can get the same results locally without publishing anything. It takes about 10 minutes.

a) Build isomorphic at the #304 merge commit:

git clone https://github.com/SalesforceCommerceCloud/commerce-sdk-isomorphic.git
cd commerce-sdk-isomorphic
git checkout 3d2679f591bad3efd4ebf8326b855a1414464f73
yarn install --frozen-lockfile --ignore-scripts
yarn run renderTemplates
yarn build:lib
npm pack    # -> commerce-sdk-isomorphic-6.0.0.tgz

--ignore-scripts only skips the prepare script, which runs the old snyk protect. These are the same steps publish.yml runs.

b) On this branch, temporarily set the isomorphic dependency in both packages/commerce-sdk-react/package.json and packages/template-retail-react-app/package.json to:

"commerce-sdk-isomorphic": "file:/absolute/path/to/commerce-sdk-isomorphic-6.0.0.tgz"

c) From the monorepo root, run npm install (not npm ci; the lockfiles don't match the tarball). Check that it worked:

node -p "require('./packages/commerce-sdk-react/node_modules/commerce-sdk-isomorphic/package.json').version"   # 6.0.0

d) Run the checks:

cd packages/commerce-sdk-react && npm run lint && npm test
cd ../template-retail-react-app && npm run lint && npm test

In the retail app, run the full npm test. If you run only some test files, the coverage threshold fails even when every test passes.

e) Revert before pushing. npm install rewrites every package-lock.json in the repo:

git checkout -- '*package.json' '*package-lock.json'

Don't commit the file: path or the lockfile changes.

I ran these steps on 5e5e71d. Typecheck passes, the retail tests for the changed areas pass, and the wire-body test passes, so the 6.0.0 helper does send login_id/channel_id. Three things fail:

  • One commerce-sdk-react test fails (inline).
  • commerce-sdk-react lint: 3 errors in src/auth/index.test.ts (inline).
  • Retail lint: 5 prettier errors in login/index.jsx:126, auth-utils.js:32 and :163, and auth-utils.test.js:124 and :148. The new ssr.js condition is probably over the line length too. npm run lint:fix fixes all of them.

Please paste the passing lint and test output into the PR description so reviewers have evidence until CI can run. We'll still need one green CI run after 6.0.0 is published, because the generated-project and Windows jobs can't be reproduced this way.

2. PR description

  • "How to Test-Drive This PR" still says (step1).
  • Please add a short rollout note: this needs to ship before SLAS enforces login_id/channel_id on /passwordless/token, and storefronts on older PWA Kit versions will fail passwordless login once SLAS enforces it.
  • The Errors bullet still says password/action rate limits map to the invalid-token UX (see the CHANGELOG comment).

const handleOtpVerification = async (pwdlessLoginToken) => {
try {
await loginPasswordless.mutateAsync({pwdlessLoginToken})
const loginId = form.getValues().email || initialEmail

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: one-click contact-info and registration now use the email that was sent to authorizePasswordless (lastEmailSentRef), but here and in login/index.jsx we read the current form value. If the shopper changes the email (even just its casing) between sending and verifying, SLAS returns a login_id mismatch and the shopper sees "invalid or expired code". Consider capturing the authorize-time email here too, for consistency.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Capturing authorize-time email here for consistency, and failing fast if no email is provided

@jeremy-jung1 jeremy-jung1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for turning this around so quickly. The last round is addressed: the test fix, the test.skip removal, lint, translations, registration error messages, authorize-time email in the modal and login page, the CHANGELOG, and the Test-Drive steps with local results all look good. Just a couple of small things left, plus an FYI.

1. FYI: I'm handling the scaffold ssr.js templates in #4092

pwa-kit-create-app has its own copies of sendMagicLinkEmail that this PR doesn't update:

  • assets/templates/@salesforce/retail-react-app/app/ssr.js.hbs
  • assets/bootstrap/js/overrides/app/ssr.js.hbs

Magic links from generated and extensible projects therefore arrive without login_id. With the new early return in login/index.jsx, magic-link login in those projects would fail without showing anything.

#4092 is stacked on your branch. It:

  • adds login_id to both templates
  • shows the landing-page error instead of dropping it
  • adds a CHANGELOG upgrade note for projects that override ssr.js

No change needed here. I'll merge #4092 right after this one so both land in the same release.

2. PR description

  • The Errors bullet under Changes still says password/action rate limits map to "the invalid-token reset-password UX". The CHANGELOG is fixed, but this bullet isn't.
  • Please add a rollout note: this needs to ship before SLAS enforces login_id/channel_id on /passwordless/token. Projects on older PWA Kit versions, or with a custom ssr.js that doesn't add login_id, will fail passwordless login once SLAS enforces it.

3. Nit: leftover blank lines from lint --fix

In components/_app/index.test.js:33 and pages/component-preview/index.test.js, lint --fix replaced the // eslint-disable-next-line react/prop-types comment with an empty line inside an object literal. Please delete the empty line, or revert those two files, since they aren't part of this change.

return {
...actual,
// eslint-disable-next-line react/prop-types

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: empty line left behind after lint --fix removed the eslint-disable comment. Please delete it, or revert this file (same in component-preview/index.test.js).

This branch has not been deployed

No deployments
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.

3 participants