Skip to content

Commit

Permalink
Allow passing custom AbortController to register and authenticate fun…
Browse files Browse the repository at this point in the history
…ctions without breaking existing behavior
  • Loading branch information
hjaber committed Nov 14, 2024
1 parent 0d83b24 commit c3003ab
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 12 deletions.
47 changes: 35 additions & 12 deletions src/client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AuthenticateOptions, AuthenticationJSON, Base64URLString, CredentialDescriptor, ExtendedAuthenticatorTransport, PublicKeyCredentialHints, RegisterOptions, RegistrationJSON, User, WebAuthnCreateOptions, WebAuthnGetOptions } from './types.js'
import { AuthenticateOptions, AuthenticationJSON, Base64URLString, CredentialDescriptor, PublicKeyCredentialHints, RegisterOptions, RegistrationJSON, User, WebAuthnCreateOptions, WebAuthnGetOptions } from './types.js'
import * as utils from './utils'

/**
Expand Down Expand Up @@ -53,6 +53,9 @@ let ongoingAuth: AbortController | null = null;
* @param {'discouraged'|'preferred'|'required'} [discoverable] A "discoverable" credential can be selected using `authenticate(...)` without providing credential IDs.
* Instead, a native pop-up will appear for user selection.
* This may have an impact on the "passkeys" user experience and syncing behavior of the key.
* @param {AbortSignal} [signal] An optional AbortSignal to allow aborting the registration process.
* If not provided, an `AbortController` (stored in `ongoingAuth`) is used to cancel any previous ongoing authentication or registration
* if another `authenticate()` or `register()` call is made.
*/
export async function register(options: RegisterOptions): Promise<RegistrationJSON> {

Expand Down Expand Up @@ -97,14 +100,23 @@ export async function register(options: RegisterOptions): Promise<RegistrationJS

console.debug(creationOptions)

if (ongoingAuth != null)
ongoingAuth.abort('Cancel ongoing authentication')
ongoingAuth = new AbortController();
let signal: AbortSignal;
if (options.signal) {
// Use the provided signal
signal = options.signal;
} else {
// Use the global ongoingAuth
if (ongoingAuth != null) {
ongoingAuth.abort('Cancel ongoing authentication');
}
ongoingAuth = new AbortController();
signal = ongoingAuth.signal;
}

const raw = await navigator.credentials.create({
publicKey: creationOptions,
signal: ongoingAuth?.signal
}) as PublicKeyCredential
signal: signal,
}) as PublicKeyCredential;
const response = raw.response as AuthenticatorAttestationResponse

ongoingAuth = null;
Expand Down Expand Up @@ -150,6 +162,9 @@ export async function isAutocompleteAvailable() {
* @param {number} [timeout=60000] Number of milliseconds the user has to respond to the biometric/PIN check.
* @param {'required'|'preferred'|'discouraged'} [userVerification='required'] Whether to prompt for biometric/PIN check or not.
* @param {boolean} [conditional] Does not return directly, but only when the user has selected a credential in the input field with `autocomplete="username webauthn"`
* @param {AbortSignal} [signal] An optional signal to abort the authentication request.
* If not provided, an `AbortController` (stored in `ongoingAuth`) is used to cancel any previous ongoing authentication or registration
* if another `authenticate()` or `register()` call is made.
*/
export async function authenticate(options: AuthenticateOptions): Promise<AuthenticationJSON> {
if (!utils.isBase64url(options.challenge))
Expand All @@ -169,16 +184,24 @@ export async function authenticate(options: AuthenticateOptions): Promise<Authen

console.debug(authOptions)

if(ongoingAuth != null)
ongoingAuth.abort('Cancel ongoing authentication')

ongoingAuth = new AbortController();
let signal: AbortSignal;
if (options.signal) {
// Use the provided signal
signal = options.signal;
} else {
// Use the global ongoingAuth
if (ongoingAuth != null) {
ongoingAuth.abort('Cancel ongoing authentication');
}
ongoingAuth = new AbortController();
signal = ongoingAuth.signal;
}

const raw = await navigator.credentials.get({
publicKey: authOptions,
mediation: options.autocomplete ? 'conditional' : undefined,
signal: ongoingAuth?.signal
}) as PublicKeyCredential
signal: signal,
}) as PublicKeyCredential;

if (raw.type != "public-key")
throw "Unexpected credential type!";
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export interface CommonOptions {
export interface RegisterOptions extends CommonOptions {
attestation?: boolean
discoverable?: ResidentKeyRequirement
signal?: AbortSignal
user: string | User
}

Expand All @@ -56,6 +57,7 @@ export interface CredentialDescriptor {
export interface AuthenticateOptions extends CommonOptions {
allowCredentials?: (CredentialDescriptor | string)[]
autocomplete?: boolean
signal?: AbortSignal
}


Expand Down

0 comments on commit c3003ab

Please sign in to comment.