Skip to content

Commit 2af7402

Browse files
committed
progress
1 parent de91a74 commit 2af7402

File tree

2 files changed

+43
-18
lines changed

2 files changed

+43
-18
lines changed

app/routes/_auth+/login.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
providerNames,
1818
} from '#app/utils/connections.tsx'
1919
import { checkHoneypot } from '#app/utils/honeypot.server.ts'
20-
import { useIsPending } from '#app/utils/misc.tsx'
20+
import { getErrorMessage, useIsPending } from '#app/utils/misc.tsx'
2121
import { PasswordSchema, UsernameSchema } from '#app/utils/user-validation.ts'
2222
import { type Route } from './+types/login.ts'
2323
import { handleNewSession } from './login.server.ts'
@@ -267,10 +267,8 @@ function PasskeyLogin({
267267
setPasskeyMessage("You're logged in! Navigating...")
268268
await navigate(verification.location ?? '/')
269269
} catch (e) {
270-
console.error('ahhhhhhhhhhhhh error', e)
271-
setError(
272-
e instanceof Error ? e.message : 'Failed to authenticate with passkey',
273-
)
270+
const errorMessage = getErrorMessage(e)
271+
setError(`Failed to authenticate with passkey: ${errorMessage}`)
274272
}
275273
}
276274

tests/e2e/passkey.test.ts

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,35 +9,54 @@ async function setupWebAuthn(page: any) {
99
const result = await client.send('WebAuthn.addVirtualAuthenticator', {
1010
options: {
1111
protocol: 'ctap2',
12-
transport: 'internal',
12+
transport: 'usb',
13+
hasResidentKey: true,
1314
hasUserVerification: true,
1415
isUserVerified: true,
16+
automaticPresenceSimulation: true,
1517
},
1618
})
1719
return { client, authenticatorId: result.authenticatorId }
1820
}
1921

22+
async function waitOnce(
23+
client: CDPSession,
24+
event: Parameters<CDPSession['once']>[0],
25+
) {
26+
let resolve: () => void
27+
client.once(event, () => resolve())
28+
return new Promise<void>((r) => {
29+
resolve = r
30+
})
31+
}
32+
2033
async function simulateSuccessfulPasskeyInput(
2134
client: CDPSession,
22-
authenticatorId: string,
2335
operationTrigger: () => Promise<void>,
2436
) {
2537
// initialize event listeners to wait for a successful passkey input event
26-
const operationCompleted = new Promise<void>((resolve) => {
27-
client.on('WebAuthn.credentialAdded', () => resolve())
28-
client.on('WebAuthn.credentialAsserted', () => resolve())
38+
let resolve: () => void
39+
const credentialAddedHandler = () => resolve()
40+
const credentialAssertedHandler = () => resolve()
41+
const operationCompleted = new Promise<void>((r) => {
42+
resolve = r
43+
client.on('WebAuthn.credentialAdded', credentialAddedHandler)
44+
client.on('WebAuthn.credentialAsserted', credentialAssertedHandler)
2945
})
3046

3147
// perform a user action that triggers passkey prompt
3248
await operationTrigger()
3349

3450
// wait to receive the event that the passkey was successfully registered or verified
3551
await operationCompleted
52+
53+
// clean up event listeners
54+
client.off('WebAuthn.credentialAdded', credentialAddedHandler)
55+
client.off('WebAuthn.credentialAsserted', credentialAssertedHandler)
3656
}
3757

3858
test('Users can register and use passkeys', async ({ page, login }) => {
39-
const password = faker.internet.password()
40-
const user = await login({ password })
59+
const user = await login()
4160

4261
const { client, authenticatorId } = await setupWebAuthn(page)
4362

@@ -51,9 +70,9 @@ test('Users can register and use passkeys', async ({ page, login }) => {
5170

5271
await page.goto('/settings/profile/passkeys')
5372

54-
await simulateSuccessfulPasskeyInput(client, authenticatorId, async () => {
55-
await page.getByRole('button', { name: /register new passkey/i }).click()
56-
})
73+
const passkeyRegisteredPromise = waitOnce(client, 'WebAuthn.credentialAdded')
74+
await page.getByRole('button', { name: /register new passkey/i }).click()
75+
await passkeyRegisteredPromise
5776

5877
// Verify the passkey appears in the UI
5978
await expect(page.getByRole('list', { name: /passkeys/i })).toBeVisible()
@@ -77,10 +96,18 @@ test('Users can register and use passkeys', async ({ page, login }) => {
7796
await page.goto('/login')
7897
const signCount1 = afterRegistrationCredentials.credentials[0].signCount
7998

80-
await simulateSuccessfulPasskeyInput(client, authenticatorId, async () => {
81-
await page.getByRole('button', { name: /login with a passkey/i }).click()
99+
const passkeyAssertedPromise = waitOnce(client, 'WebAuthn.credentialAsserted')
100+
101+
await page.getByRole('button', { name: /login with a passkey/i }).click()
102+
103+
// Check for error message before waiting for completion
104+
const errorLocator = page.getByText(/failed to authenticate/i)
105+
const errorPromise = errorLocator.waitFor({ timeout: 1000 }).then(() => {
106+
throw new Error('Passkey authentication failed')
82107
})
83108

109+
await Promise.race([passkeyAssertedPromise, errorPromise])
110+
84111
// Verify successful login
85112
await expect(
86113
page.getByRole('link', { name: user.name ?? user.username }),
@@ -116,7 +143,7 @@ test('Users can register and use passkeys', async ({ page, login }) => {
116143

117144
// Try logging in with the deleted passkey
118145
await page.goto('/login')
119-
await simulateSuccessfulPasskeyInput(client, authenticatorId, async () => {
146+
await simulateSuccessfulPasskeyInput(client, async () => {
120147
await page.getByRole('button', { name: /login with a passkey/i }).click()
121148
})
122149

0 commit comments

Comments
 (0)