| |
|
|
| import { StrictOmit } from './types' |
| import { isValidDomain } from './webauthn' |
| import { |
| PublicKeyCredentialCreationOptionsFuture, |
| PublicKeyCredentialRequestOptionsFuture, |
| } from './webauthn.dom' |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export class WebAuthnError extends Error { |
| code: WebAuthnErrorCode |
|
|
| protected __isWebAuthnError = true |
|
|
| constructor({ |
| message, |
| code, |
| cause, |
| name, |
| }: { |
| message: string |
| code: WebAuthnErrorCode |
| cause?: Error | unknown |
| name?: string |
| }) { |
| |
| super(message, { cause }) |
| this.name = name ?? (cause instanceof Error ? cause.name : undefined) ?? 'Unknown Error' |
| this.code = code |
| } |
|
|
| toJSON(): { |
| name: string |
| message: string |
| code: WebAuthnErrorCode |
| } { |
| return { |
| name: this.name, |
| message: this.message, |
| code: this.code, |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| export class WebAuthnUnknownError extends WebAuthnError { |
| originalError: unknown |
|
|
| constructor(message: string, originalError: unknown) { |
| super({ |
| code: 'ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY', |
| cause: originalError, |
| message, |
| }) |
| this.name = 'WebAuthnUnknownError' |
| this.originalError = originalError |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| export function isWebAuthnError(error: unknown): error is WebAuthnError { |
| return typeof error === 'object' && error !== null && '__isWebAuthnError' in error |
| } |
|
|
| |
| |
| |
| |
| |
| export type WebAuthnErrorCode = |
| | 'ERROR_CEREMONY_ABORTED' |
| | 'ERROR_INVALID_DOMAIN' |
| | 'ERROR_INVALID_RP_ID' |
| | 'ERROR_INVALID_USER_ID_LENGTH' |
| | 'ERROR_MALFORMED_PUBKEYCREDPARAMS' |
| | 'ERROR_AUTHENTICATOR_GENERAL_ERROR' |
| | 'ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT' |
| | 'ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT' |
| | 'ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED' |
| | 'ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG' |
| | 'ERROR_AUTO_REGISTER_USER_VERIFICATION_FAILURE' |
| | 'ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY' |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function identifyRegistrationError({ |
| error, |
| options, |
| }: { |
| error: Error |
| options: StrictOmit<CredentialCreationOptions, 'publicKey'> & { |
| publicKey: PublicKeyCredentialCreationOptionsFuture |
| } |
| }): WebAuthnError { |
| const { publicKey } = options |
|
|
| if (!publicKey) { |
| throw Error('options was missing required publicKey property') |
| } |
|
|
| if (error.name === 'AbortError') { |
| if (options.signal instanceof AbortSignal) { |
| |
| return new WebAuthnError({ |
| message: 'Registration ceremony was sent an abort signal', |
| code: 'ERROR_CEREMONY_ABORTED', |
| cause: error, |
| }) |
| } |
| } else if (error.name === 'ConstraintError') { |
| if (publicKey.authenticatorSelection?.requireResidentKey === true) { |
| |
| return new WebAuthnError({ |
| message: |
| 'Discoverable credentials were required but no available authenticator supported it', |
| code: 'ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT', |
| cause: error, |
| }) |
| } else if ( |
| |
| options.mediation === 'conditional' && |
| publicKey.authenticatorSelection?.userVerification === 'required' |
| ) { |
| |
| return new WebAuthnError({ |
| message: |
| 'User verification was required during automatic registration but it could not be performed', |
| code: 'ERROR_AUTO_REGISTER_USER_VERIFICATION_FAILURE', |
| cause: error, |
| }) |
| } else if (publicKey.authenticatorSelection?.userVerification === 'required') { |
| |
| return new WebAuthnError({ |
| message: 'User verification was required but no available authenticator supported it', |
| code: 'ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT', |
| cause: error, |
| }) |
| } |
| } else if (error.name === 'InvalidStateError') { |
| |
| |
| return new WebAuthnError({ |
| message: 'The authenticator was previously registered', |
| code: 'ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED', |
| cause: error, |
| }) |
| } else if (error.name === 'NotAllowedError') { |
| |
| |
| |
| |
| return new WebAuthnError({ |
| message: error.message, |
| code: 'ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY', |
| cause: error, |
| }) |
| } else if (error.name === 'NotSupportedError') { |
| const validPubKeyCredParams = publicKey.pubKeyCredParams.filter( |
| (param) => param.type === 'public-key' |
| ) |
|
|
| if (validPubKeyCredParams.length === 0) { |
| |
| return new WebAuthnError({ |
| message: 'No entry in pubKeyCredParams was of type "public-key"', |
| code: 'ERROR_MALFORMED_PUBKEYCREDPARAMS', |
| cause: error, |
| }) |
| } |
|
|
| |
| return new WebAuthnError({ |
| message: |
| 'No available authenticator supported any of the specified pubKeyCredParams algorithms', |
| code: 'ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG', |
| cause: error, |
| }) |
| } else if (error.name === 'SecurityError') { |
| const effectiveDomain = window.location.hostname |
| if (!isValidDomain(effectiveDomain)) { |
| |
| return new WebAuthnError({ |
| message: `${window.location.hostname} is an invalid domain`, |
| code: 'ERROR_INVALID_DOMAIN', |
| cause: error, |
| }) |
| } else if (publicKey.rp.id !== effectiveDomain) { |
| |
| return new WebAuthnError({ |
| message: `The RP ID "${publicKey.rp.id}" is invalid for this domain`, |
| code: 'ERROR_INVALID_RP_ID', |
| cause: error, |
| }) |
| } |
| } else if (error.name === 'TypeError') { |
| if (publicKey.user.id.byteLength < 1 || publicKey.user.id.byteLength > 64) { |
| |
| return new WebAuthnError({ |
| message: 'User ID was not between 1 and 64 characters', |
| code: 'ERROR_INVALID_USER_ID_LENGTH', |
| cause: error, |
| }) |
| } |
| } else if (error.name === 'UnknownError') { |
| |
| |
| return new WebAuthnError({ |
| message: |
| 'The authenticator was unable to process the specified options, or could not create a new credential', |
| code: 'ERROR_AUTHENTICATOR_GENERAL_ERROR', |
| cause: error, |
| }) |
| } |
|
|
| return new WebAuthnError({ |
| message: 'a Non-Webauthn related error has occurred', |
| code: 'ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY', |
| cause: error, |
| }) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function identifyAuthenticationError({ |
| error, |
| options, |
| }: { |
| error: Error |
| options: StrictOmit<CredentialRequestOptions, 'publicKey'> & { |
| publicKey: PublicKeyCredentialRequestOptionsFuture |
| } |
| }): WebAuthnError { |
| const { publicKey } = options |
|
|
| if (!publicKey) { |
| throw Error('options was missing required publicKey property') |
| } |
|
|
| if (error.name === 'AbortError') { |
| if (options.signal instanceof AbortSignal) { |
| |
| return new WebAuthnError({ |
| message: 'Authentication ceremony was sent an abort signal', |
| code: 'ERROR_CEREMONY_ABORTED', |
| cause: error, |
| }) |
| } |
| } else if (error.name === 'NotAllowedError') { |
| |
| |
| |
| |
| return new WebAuthnError({ |
| message: error.message, |
| code: 'ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY', |
| cause: error, |
| }) |
| } else if (error.name === 'SecurityError') { |
| const effectiveDomain = window.location.hostname |
| if (!isValidDomain(effectiveDomain)) { |
| |
| return new WebAuthnError({ |
| message: `${window.location.hostname} is an invalid domain`, |
| code: 'ERROR_INVALID_DOMAIN', |
| cause: error, |
| }) |
| } else if (publicKey.rpId !== effectiveDomain) { |
| |
| return new WebAuthnError({ |
| message: `The RP ID "${publicKey.rpId}" is invalid for this domain`, |
| code: 'ERROR_INVALID_RP_ID', |
| cause: error, |
| }) |
| } |
| } else if (error.name === 'UnknownError') { |
| |
| |
| return new WebAuthnError({ |
| message: |
| 'The authenticator was unable to process the specified options, or could not create a new assertion signature', |
| code: 'ERROR_AUTHENTICATOR_GENERAL_ERROR', |
| cause: error, |
| }) |
| } |
|
|
| return new WebAuthnError({ |
| message: 'a Non-Webauthn related error has occurred', |
| code: 'ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY', |
| cause: error, |
| }) |
| } |
|
|