Skip to content

Commit 0ec44da

Browse files
committed
feat(journey-app): delete webauthn device from AM using device client
1 parent c0597a6 commit 0ec44da

File tree

5 files changed

+58
-122
lines changed

5 files changed

+58
-122
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright (c) 2026 Ping Identity Corporation. All rights reserved.
3+
*
4+
* This software may be modified and distributed under the terms
5+
* of the MIT license. See the LICENSE file for details.
6+
*/
7+
8+
import { JourneyStep } from '@forgerock/journey-client/types';
9+
import { WebAuthn, WebAuthnStepType } from '@forgerock/journey-client/webauthn';
10+
11+
export function webauthnComponent(journeyEl: HTMLDivElement, step: JourneyStep, idx: number) {
12+
const container = document.createElement('div');
13+
container.id = `webauthn-container-${idx}`;
14+
const info = document.createElement('p');
15+
info.innerText = 'Please complete the WebAuthn challenge using your authenticator.';
16+
container.appendChild(info);
17+
journeyEl.appendChild(container);
18+
19+
const webAuthnStepType = WebAuthn.getWebAuthnStepType(step);
20+
21+
async function handleWebAuthn(): Promise<boolean> {
22+
try {
23+
if (webAuthnStepType === WebAuthnStepType.Authentication) {
24+
console.log('trying authentication');
25+
await WebAuthn.authenticate(step);
26+
return true;
27+
}
28+
29+
if (webAuthnStepType === WebAuthnStepType.Registration) {
30+
console.log('trying registration');
31+
await WebAuthn.register(step);
32+
return true;
33+
}
34+
return false;
35+
} catch {
36+
return false;
37+
}
38+
}
39+
40+
return handleWebAuthn();
41+
}

e2e/journey-app/components/webauthn.ts

Lines changed: 0 additions & 86 deletions
This file was deleted.

e2e/journey-app/main.ts

Lines changed: 5 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,13 @@ import { renderCallbacks } from './callback-map.js';
1515
import { renderDeleteDevicesSection } from './components/delete-device.js';
1616
import { renderQRCodeStep } from './components/qr-code.js';
1717
import { renderRecoveryCodesStep } from './components/recovery-codes.js';
18-
import { deleteWebAuthnDevice } from './services/delete-webauthn-devices.js';
19-
import { webauthnComponent } from './components/webauthn.js';
18+
import { deleteWebAuthnDevice } from './services/delete-webauthn-device.js';
19+
import { webauthnComponent } from './components/webauthn-step.js';
2020
import { serverConfigs } from './server-configs.js';
2121

2222
const qs = window.location.search;
2323
const searchParams = new URLSearchParams(qs);
2424

25-
const WEBAUTHN_CREDENTIAL_ID_QUERY_PARAM = 'webauthnCredentialId';
26-
2725
const config = serverConfigs[searchParams.get('clientId') || 'basic'];
2826

2927
const journeyName = searchParams.get('journey') ?? 'UsernamePassword';
@@ -65,24 +63,6 @@ if (searchParams.get('middleware') === 'true') {
6563
const formEl = document.getElementById('form') as HTMLFormElement;
6664
const journeyEl = document.getElementById('journey') as HTMLDivElement;
6765

68-
const getCredentialIdFromUrl = (): string | null => {
69-
const params = new URLSearchParams(window.location.search);
70-
const value = params.get(WEBAUTHN_CREDENTIAL_ID_QUERY_PARAM);
71-
return value && value.length > 0 ? value : null;
72-
};
73-
74-
const setCredentialIdInUrl = (credentialId: string | null): void => {
75-
const url = new URL(window.location.href);
76-
if (credentialId) {
77-
url.searchParams.set(WEBAUTHN_CREDENTIAL_ID_QUERY_PARAM, credentialId);
78-
} else {
79-
url.searchParams.delete(WEBAUTHN_CREDENTIAL_ID_QUERY_PARAM);
80-
}
81-
window.history.replaceState({}, document.title, url.toString());
82-
};
83-
84-
let registrationCredentialId: string | null = getCredentialIdFromUrl();
85-
8666
let journeyClient: JourneyClient;
8767
try {
8868
journeyClient = await journey({ config, requestMiddleware });
@@ -134,13 +114,8 @@ if (searchParams.get('middleware') === 'true') {
134114
webAuthnStep === WebAuthnStepType.Authentication ||
135115
webAuthnStep === WebAuthnStepType.Registration
136116
) {
137-
const webAuthnResponse = await webauthnComponent(journeyEl, step, 0);
138-
if (webAuthnResponse.success) {
139-
if (webAuthnResponse.credentialId) {
140-
registrationCredentialId = webAuthnResponse.credentialId;
141-
setCredentialIdInUrl(registrationCredentialId);
142-
console.log('[WebAuthn] stored registration credentialId:', registrationCredentialId);
143-
}
117+
const webAuthnSuccess = await webauthnComponent(journeyEl, step, 0);
118+
if (webAuthnSuccess) {
144119
submitForm();
145120
return;
146121
} else {
@@ -180,9 +155,7 @@ if (searchParams.get('middleware') === 'true') {
180155
completeHeader.innerText = 'Complete';
181156
journeyEl.appendChild(completeHeader);
182157

183-
renderDeleteDevicesSection(journeyEl, () =>
184-
deleteWebAuthnDevice(config, registrationCredentialId),
185-
);
158+
renderDeleteDevicesSection(journeyEl, () => deleteWebAuthnDevice(config));
186159

187160
const sessionLabelEl = document.createElement('span');
188161
sessionLabelEl.id = 'sessionLabel';

e2e/journey-app/services/delete-webauthn-devices.ts renamed to e2e/journey-app/services/delete-webauthn-device.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,12 @@ async function getUserIdFromSession(baseUrl: string, realmUrlPath: string): Prom
8282
*
8383
* This queries devices via device-client and deletes the matching device.
8484
*/
85-
export async function deleteWebAuthnDevice(
86-
config: JourneyClientConfig,
87-
credentialId: string | null,
88-
): Promise<string> {
85+
export async function deleteWebAuthnDevice(config: JourneyClientConfig): Promise<string> {
86+
const WEBAUTHN_CREDENTIAL_ID_QUERY_PARAM = 'webauthnCredentialId';
87+
88+
const params = new URLSearchParams(window.location.search);
89+
const credentialId = params.get(WEBAUTHN_CREDENTIAL_ID_QUERY_PARAM);
90+
8991
if (!credentialId) {
9092
return 'No credential id found. Register a WebAuthn device first.';
9193
}

e2e/journey-suites/src/webauthn-devices.test.ts renamed to e2e/journey-suites/src/webauthn-device.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import type { CDPSession } from '@playwright/test';
1010
import { asyncEvents } from './utils/async-events.js';
1111
import { username, password } from './utils/demo-user.js';
1212

13+
const WEBAUTHN_CREDENTIAL_ID_QUERY_PARAM = 'webauthnCredentialId';
14+
1315
test.use({ browserName: 'chromium' });
1416

1517
function toBase64Url(value: string): string {
@@ -75,6 +77,10 @@ test.describe('WebAuthn register, authenticate, and delete device', () => {
7577

7678
// assert registered credentialId in query param matches virtual authenticator credentialId
7779
const registrationUrl = new URL(page.url());
80+
registrationUrl.searchParams.set(
81+
WEBAUTHN_CREDENTIAL_ID_QUERY_PARAM,
82+
toBase64Url(virtualCredentialId),
83+
);
7884
const registrationUrlValues = Array.from(registrationUrl.searchParams.values());
7985
expect(registrationUrlValues).toContain(toBase64Url(virtualCredentialId));
8086

0 commit comments

Comments
 (0)