Skip to content

Commit 767acfc

Browse files
committed
Refactor passkey registration handling in AuthModal and related components; replace showToast with showRegisterPasskeyToast for improved clarity and functionality. Update tests to validate passkey registration toast display after successful login, ensuring proper configuration checks and UI rendering.
1 parent b16b759 commit 767acfc

File tree

7 files changed

+334
-114
lines changed

7 files changed

+334
-114
lines changed

packages/template-retail-react-app/app/hooks/use-auth-modal.js

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ export const AuthModal = ({
9090
const login = useAuthHelper(AuthHelpers.LoginRegisteredUserB2C)
9191
const register = useAuthHelper(AuthHelpers.Register)
9292
const {locale} = useMultiSite()
93-
const config = getConfig()
9493

9594
const {getPasswordResetToken} = usePasswordReset()
9695
const authorizePasswordlessLogin = useAuthHelper(AuthHelpers.AuthorizePasswordless)
@@ -105,7 +104,7 @@ export const AuthModal = ({
105104
)
106105
const mergeBasket = useShopperBasketsMutation('mergeBasket')
107106

108-
const {showToast} = usePasskeyRegistration()
107+
const {showRegisterPasskeyToast} = usePasskeyRegistration()
109108

110109
const handlePasswordlessLogin = async (email) => {
111110
try {
@@ -281,23 +280,8 @@ export const AuthModal = ({
281280
onClose()
282281
setIsOtpAuthOpen(false)
283282

284-
if (config?.app?.login?.passkey?.enabled) {
285-
// Show passkey registration modal only if Webauthn feature flag is enabled and compatible with the browser
286-
if (
287-
window.PublicKeyCredential &&
288-
window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&
289-
window.PublicKeyCredential.isConditionalMediationAvailable
290-
) {
291-
Promise.all([
292-
window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),
293-
window.PublicKeyCredential.isConditionalMediationAvailable()
294-
]).then((results) => {
295-
if (results.every((r) => r === true)) {
296-
showToast()
297-
}
298-
})
299-
}
300-
}
283+
// Show passkey registration prompt if supported
284+
showRegisterPasskeyToast()
301285

302286
// Show a toast only for those registed users returning to the site.
303287
// Only show toast when customer data is available (user is logged in and data is loaded)
@@ -431,7 +415,7 @@ AuthModal.propTypes = {
431415
*/
432416
export const useAuthModal = (initialView = LOGIN_VIEW) => {
433417
const {isOpen, onOpen, onClose} = useDisclosure()
434-
const {passwordless = {}, social = {}, passkey = {}} = getConfig().app.login || {}
418+
const {passwordless = {}, social = {}} = getConfig().app.login || {}
435419

436420
return {
437421
initialView,

packages/template-retail-react-app/app/hooks/use-auth-modal.test.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -922,6 +922,74 @@ describe('Passkey login', () => {
922922
expect(screen.queryByText(/Welcome back/i)).not.toBeInTheDocument()
923923
})
924924
})
925+
926+
describe('Passkey Registration', () => {
927+
beforeEach(() => {
928+
getConfig.mockReturnValue({
929+
...mockConfig,
930+
app: {
931+
...mockConfig.app,
932+
login: {
933+
...mockConfig.app.login,
934+
passkey: {enabled: true}
935+
}
936+
}
937+
})
938+
939+
// Mock WebAuthn API
940+
global.PublicKeyCredential = {
941+
isUserVerifyingPlatformAuthenticatorAvailable: jest.fn().mockResolvedValue(true),
942+
isConditionalMediationAvailable: jest.fn().mockResolvedValue(true)
943+
}
944+
global.window.PublicKeyCredential = global.PublicKeyCredential
945+
946+
global.server.use(
947+
rest.post('*/oauth2/token', (req, res, ctx) =>
948+
res(
949+
ctx.delay(0),
950+
ctx.json({
951+
customer_id: 'registeredCustomerId',
952+
access_token: registerUserToken,
953+
refresh_token: 'testrefeshtoken',
954+
usid: 'testusid',
955+
enc_user_id: 'testEncUserId',
956+
id_token: 'testIdToken'
957+
})
958+
)
959+
)
960+
)
961+
})
962+
963+
afterEach(() => {
964+
delete global.PublicKeyCredential
965+
delete global.window.PublicKeyCredential
966+
})
967+
968+
test('shows passkey registration toast after login', async () => {
969+
const {user} = renderWithProviders(<MockedComponent isPasswordlessEnabled={true} />)
970+
const validEmail = 'test@salesforce.com'
971+
const validPassword = 'Password123!'
972+
973+
const trigger = screen.getByText(/open modal/i)
974+
await user.click(trigger)
975+
976+
await waitFor(() => {
977+
expect(screen.getByText(/Continue/i)).toBeInTheDocument()
978+
})
979+
980+
await user.type(screen.getByLabelText('Email'), validEmail)
981+
await user.click(screen.getByText(/password/i))
982+
await user.type(screen.getByLabelText('Password'), validPassword)
983+
await user.keyboard('{Enter}')
984+
985+
// Create passkey toast is shown after login
986+
await waitFor(() => {
987+
// 2 matches are found for the toast
988+
const toasts = screen.getAllByText(/Create Passkey/i)
989+
expect(toasts.length).toBeGreaterThanOrEqual(1)
990+
})
991+
})
992+
})
925993
})
926994

927995
describe('Reset password', function () {

packages/template-retail-react-app/app/hooks/use-passkey-login.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export const usePasskeyLogin = () => {
3333
}
3434

3535
// Check if conditional mediation is available. Conditional mediation is a feature of the WebAuthn API that allows passkeys to appear in the browser's standard autofill suggestions, alongside saved passwords. This allows users to sign in with a passkey using the standard username input field, rather than clicking a dedicated passkey login button.
36-
// https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/isConditionalMediationAvailable
36+
// https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/isConditionalMediationAvailable_static
3737
const isCMA = await window.PublicKeyCredential.isConditionalMediationAvailable()
3838
if (!isCMA) {
3939
return

packages/template-retail-react-app/app/hooks/use-passkey-registration.js

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,47 @@ import {
1414
useToast
1515
} from '@salesforce/retail-react-app/app/components/shared/ui'
1616
import {usePasskeyRegistrationContext} from '@salesforce/retail-react-app/app/contexts/passkey-registration-provider'
17+
import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config'
1718

1819
/**
1920
* Custom hook to manage passkey registration prompt (toast and modal)
20-
* @returns {Object} Object containing showToast function and passkey modal state
21+
* @returns {Object} Object containing showToast and passkey modal state
2122
*/
2223
export const usePasskeyRegistration = () => {
2324
const toast = useToast()
2425
const {passkeyModal} = usePasskeyRegistrationContext()
2526
const {formatMessage} = useIntl()
2627

27-
const showToast = () => {
28+
/**
29+
* Shows the passkey registration toast only if passkey is enabled and the browser
30+
* supports WebAuthn (platform authenticator and conditional mediation).
31+
* Returns a Promise that resolves when the check (and optional toast) is complete.
32+
* @returns {Promise<void>}
33+
*/
34+
const showRegisterPasskeyToast = async () => {
35+
const config = getConfig()
36+
37+
// Check if passkey is enabled in config
38+
if (!config?.app?.login?.passkey?.enabled) return
39+
40+
// Check if the browser supports user verifying platform authenticator and conditional mediation
41+
// User verifying platform authenticator is a feature of the WebAuthn API that allows the browser to use a platform authenticator to verify the user's identity.
42+
// Conditional mediation is a feature of the WebAuthn API that allows passkeys to appear in the browser's standard autofill suggestions, alongside saved passwords. This allows users to sign in with a passkey using the standard username input field, rather than clicking a dedicated passkey login button.
43+
// https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/isUserVerifyingPlatformAuthenticatorAvailable_static
44+
// https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/isConditionalMediationAvailable_static
45+
if (
46+
!window.PublicKeyCredential?.isUserVerifyingPlatformAuthenticatorAvailable ||
47+
!window.PublicKeyCredential?.isConditionalMediationAvailable
48+
) {
49+
return
50+
}
51+
52+
const [platformAvailable, conditionalAvailable] = await Promise.all([
53+
window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),
54+
window.PublicKeyCredential.isConditionalMediationAvailable()
55+
])
56+
if (!platformAvailable || !conditionalAvailable) return
57+
2858
toast({
2959
position: 'top-right',
3060
duration: 9000,
@@ -75,7 +105,7 @@ export const usePasskeyRegistration = () => {
75105
}
76106

77107
return {
78-
showToast,
108+
showRegisterPasskeyToast,
79109
passkeyModal
80110
}
81111
}

0 commit comments

Comments
 (0)