Skip to content

Commit ebaaff7

Browse files
Thomas Schofieldthomas-schofield-fr
authored andcommitted
fix: implementation of conditional UI for AME-34348 AME-34340
1 parent 04ae8bb commit ebaaff7

5 files changed

Lines changed: 44 additions & 47 deletions

File tree

packages/javascript-sdk/src/fr-webauthn/fr-webauthn.mock.data.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@ export const webAuthnAuthConditionalMetaCallback = {
505505
_allowCredentials: [],
506506
timeout: 60000,
507507
userVerification: 'preferred',
508-
conditionalWebAuthn: true,
508+
mediation: 'conditional',
509509
relyingPartyId: '',
510510
_relyingPartyId: 'example.com',
511511
extensions: {},

packages/javascript-sdk/src/fr-webauthn/fr-webauthn.test.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ describe('Test FRWebAuthn class with Conditional UI', () => {
149149
_allowCredentials: [],
150150
timeout: 60000,
151151
userVerification: 'preferred',
152-
conditionalWebAuthn: true,
152+
mediation: 'conditional',
153153
relyingPartyId: '',
154154
_relyingPartyId: 'example.com',
155155
extensions: {},
@@ -180,19 +180,10 @@ describe('Test FRWebAuthn class with Conditional UI', () => {
180180
vi.spyOn(window.PublicKeyCredential, 'isConditionalMediationAvailable').mockResolvedValue(
181181
false,
182182
);
183-
// FIX APPLIED HERE: Added block comment to empty function
184-
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {
185-
/* empty */
186-
});
187183
const getSpy = vi.spyOn(navigator.credentials, 'get');
188184

189185
// Attempt to authenticate with conditional UI requested
190-
await FRWebAuthn.getAuthenticationCredential({}, true);
191-
192-
// Expect a warning to be logged
193-
expect(consoleSpy).toHaveBeenCalledWith(
194-
'Conditional UI was requested, but is not supported by this browser.',
195-
);
186+
await FRWebAuthn.getAuthenticationCredential({});
196187

197188
// Expect the call to navigator.credentials.get to NOT have the mediation property
198189
expect(getSpy).toHaveBeenCalledWith(
@@ -208,7 +199,9 @@ describe('Test FRWebAuthn class with Conditional UI', () => {
208199
const getSpy = vi.spyOn(navigator.credentials, 'get');
209200

210201
// Attempt to authenticate with conditional UI requested
211-
await FRWebAuthn.getAuthenticationCredential({}, true);
202+
await FRWebAuthn.getAuthenticationCredential({
203+
mediation: 'conditional',
204+
});
212205

213206
// Expect the call to navigator.credentials.get to have the mediation property
214207
expect(getSpy).toHaveBeenCalledWith(

packages/javascript-sdk/src/fr-webauthn/index.ts

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ type WebAuthnMetadata = WebAuthnAuthenticationMetadata | WebAuthnRegistrationMet
4848
type WebAuthnTextOutput = WebAuthnTextOutputRegistration;
4949
const TWO_SECOND = 2000;
5050

51+
declare global {
52+
interface Window {
53+
PingWebAuthnAbortController: AbortController;
54+
}
55+
}
56+
5157
/**
5258
* Utility for integrating a web browser's WebAuthn API.
5359
*
@@ -151,27 +157,30 @@ abstract class FRWebAuthn {
151157

152158
try {
153159
let publicKey: PublicKeyCredentialRequestOptions;
154-
let useConditionalUI = false;
155160

156161
if (metadataCallback) {
157162
const meta = metadataCallback.getOutputValue('data') as WebAuthnAuthenticationMetadata;
163+
const mediation = meta.mediation as CredentialMediationRequirement;
164+
165+
if (mediation === 'conditional') {
166+
const isConditionalSupported = await this.isConditionalUISupported();
167+
if (!isConditionalSupported) {
168+
const e = new Error(
169+
'Conditional UI was requested, but is not supported by this browser.',
170+
);
171+
e.name = WebAuthnOutcomeType.NotSupportedError;
172+
throw e;
173+
}
174+
}
158175

159-
// Check if server indicates conditional UI should be used
160-
useConditionalUI = meta.conditional === 'true';
161176
publicKey = this.createAuthenticationPublicKey(meta);
162177

163-
credential = await this.getAuthenticationCredential(
164-
publicKey as PublicKeyCredentialRequestOptions,
165-
useConditionalUI,
166-
);
178+
credential = await this.getAuthenticationCredential({ publicKey, mediation });
167179
outcome = this.getAuthenticationOutcome(credential);
168180
} else if (textOutputCallback) {
169181
publicKey = parseWebAuthnAuthenticateText(textOutputCallback.getMessage());
170182

171-
credential = await this.getAuthenticationCredential(
172-
publicKey as PublicKeyCredentialRequestOptions,
173-
false, // Script-based callbacks don't support conditional UI
174-
);
183+
credential = await this.getAuthenticationCredential({ publicKey });
175184
outcome = this.getAuthenticationOutcome(credential);
176185
} else {
177186
throw new Error('No Credential found from Public Key');
@@ -349,37 +358,23 @@ abstract class FRWebAuthn {
349358
/**
350359
* Retrieves the credential from the browser Web Authentication API.
351360
*
352-
* @param options The public key options associated with the request
353-
* @param useConditionalUI Whether to use conditional UI (autofill)
361+
* @param options The options associated with the request
354362
* @return The credential
355363
*/
356364
public static async getAuthenticationCredential(
357-
options: PublicKeyCredentialRequestOptions,
358-
useConditionalUI = false,
365+
options: CredentialRequestOptions,
359366
): Promise<PublicKeyCredential | null> {
360367
// Feature check before we attempt authenticating
361368
if (!window.PublicKeyCredential) {
362369
const e = new Error('PublicKeyCredential not supported by this browser');
363370
e.name = WebAuthnOutcomeType.NotSupportedError;
364371
throw e;
365372
}
366-
// Build the credential request options
367-
const credentialRequestOptions: CredentialRequestOptions = {
368-
publicKey: options,
369-
};
370373

371-
// Add conditional mediation if requested and supported
372-
if (useConditionalUI) {
373-
const isConditionalSupported = await this.isConditionalUISupported();
374-
if (isConditionalSupported) {
375-
credentialRequestOptions.mediation = 'conditional' as CredentialMediationRequirement;
376-
} else {
377-
// eslint-disable-next-line no-console
378-
FRLogger.warn('Conditional UI was requested, but is not supported by this browser.');
379-
}
380-
}
381-
382-
const credential = await navigator.credentials.get(credentialRequestOptions);
374+
const credential = await navigator.credentials.get({
375+
...options,
376+
signal: this.createAbortController().signal,
377+
});
383378
return credential as PublicKeyCredential;
384379
}
385380

@@ -599,6 +594,14 @@ abstract class FRWebAuthn {
599594
},
600595
};
601596
}
597+
598+
private static createAbortController() {
599+
window.PingWebAuthnAbortController?.abort();
600+
601+
const abortController = new AbortController();
602+
window.PingWebAuthnAbortController = abortController;
603+
return abortController;
604+
}
602605
}
603606

604607
export default FRWebAuthn;
@@ -608,4 +611,4 @@ export type {
608611
WebAuthnCallbacks,
609612
WebAuthnRegistrationMetadata,
610613
};
611-
export { WebAuthnOutcome, WebAuthnStepType };
614+
export { WebAuthnOutcome, WebAuthnOutcomeType, WebAuthnStepType };

packages/javascript-sdk/src/fr-webauthn/interfaces.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ interface WebAuthnAuthenticationMetadata {
8686
_relyingPartyId?: string;
8787
timeout: number;
8888
userVerification: UserVerificationType;
89-
conditional?: string;
89+
mediation?: string;
9090
extensions?: Record<string, unknown>;
9191
_type?: 'WebAuthn';
9292
supportsJsonResponse?: boolean;

packages/javascript-sdk/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ import type {
6161
WebAuthnCallbacks,
6262
WebAuthnRegistrationMetadata,
6363
} from './fr-webauthn';
64-
import FRWebAuthn, { WebAuthnOutcome, WebAuthnStepType } from './fr-webauthn';
64+
import FRWebAuthn, { WebAuthnOutcome, WebAuthnOutcomeType, WebAuthnStepType } from './fr-webauthn';
6565
import HttpClient from './http-client';
6666
import type {
6767
GetAuthorizationUrlOptions,
@@ -160,5 +160,6 @@ export {
160160
ValidatedCreatePasswordCallback,
161161
ValidatedCreateUsernameCallback,
162162
WebAuthnOutcome,
163+
WebAuthnOutcomeType,
163164
WebAuthnStepType,
164165
};

0 commit comments

Comments
 (0)