simplewebauthn-browser.js
| 1 | // node_modules/@simplewebauthn/browser/esm/helpers/bufferToBase64URLString.js |
| 2 | function bufferToBase64URLString(buffer) { |
| 3 | const bytes = new Uint8Array(buffer); |
| 4 | let str = ""; |
| 5 | for (const charCode of bytes) { |
| 6 | str += String.fromCharCode(charCode); |
| 7 | } |
| 8 | const base64String = btoa(str); |
| 9 | return base64String.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); |
| 10 | } |
| 11 | |
| 12 | // node_modules/@simplewebauthn/browser/esm/helpers/base64URLStringToBuffer.js |
| 13 | function base64URLStringToBuffer(base64URLString) { |
| 14 | const base64 = base64URLString.replace(/-/g, "+").replace(/_/g, "/"); |
| 15 | const padLength = (4 - base64.length % 4) % 4; |
| 16 | const padded = base64.padEnd(base64.length + padLength, "="); |
| 17 | const binary = atob(padded); |
| 18 | const buffer = new ArrayBuffer(binary.length); |
| 19 | const bytes = new Uint8Array(buffer); |
| 20 | for (let i = 0;i < binary.length; i++) { |
| 21 | bytes[i] = binary.charCodeAt(i); |
| 22 | } |
| 23 | return buffer; |
| 24 | } |
| 25 | |
| 26 | // node_modules/@simplewebauthn/browser/esm/helpers/browserSupportsWebAuthn.js |
| 27 | function browserSupportsWebAuthn() { |
| 28 | return _browserSupportsWebAuthnInternals.stubThis(globalThis?.PublicKeyCredential !== undefined && typeof globalThis.PublicKeyCredential === "function"); |
| 29 | } |
| 30 | var _browserSupportsWebAuthnInternals = { |
| 31 | stubThis: (value) => value |
| 32 | }; |
| 33 | |
| 34 | // node_modules/@simplewebauthn/browser/esm/helpers/toPublicKeyCredentialDescriptor.js |
| 35 | function toPublicKeyCredentialDescriptor(descriptor) { |
| 36 | const { id } = descriptor; |
| 37 | return { |
| 38 | ...descriptor, |
| 39 | id: base64URLStringToBuffer(id), |
| 40 | transports: descriptor.transports |
| 41 | }; |
| 42 | } |
| 43 | |
| 44 | // node_modules/@simplewebauthn/browser/esm/helpers/isValidDomain.js |
| 45 | function isValidDomain(hostname) { |
| 46 | return hostname === "localhost" || /^((xn--[a-z0-9-]+|[a-z0-9]+(-[a-z0-9]+)*)\.)+([a-z]{2,}|xn--[a-z0-9-]+)$/i.test(hostname); |
| 47 | } |
| 48 | |
| 49 | // node_modules/@simplewebauthn/browser/esm/helpers/webAuthnError.js |
| 50 | class WebAuthnError extends Error { |
| 51 | constructor({ message, code, cause, name }) { |
| 52 | super(message, { cause }); |
| 53 | Object.defineProperty(this, "code", { |
| 54 | enumerable: true, |
| 55 | configurable: true, |
| 56 | writable: true, |
| 57 | value: undefined |
| 58 | }); |
| 59 | this.name = name ?? cause.name; |
| 60 | this.code = code; |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | // node_modules/@simplewebauthn/browser/esm/helpers/identifyRegistrationError.js |
| 65 | function identifyRegistrationError({ error, options }) { |
| 66 | const { publicKey } = options; |
| 67 | if (!publicKey) { |
| 68 | throw Error("options was missing required publicKey property"); |
| 69 | } |
| 70 | if (error.name === "AbortError") { |
| 71 | if (options.signal instanceof AbortSignal) { |
| 72 | return new WebAuthnError({ |
| 73 | message: "Registration ceremony was sent an abort signal", |
| 74 | code: "ERROR_CEREMONY_ABORTED", |
| 75 | cause: error |
| 76 | }); |
| 77 | } |
| 78 | } else if (error.name === "ConstraintError") { |
| 79 | if (publicKey.authenticatorSelection?.requireResidentKey === true) { |
| 80 | return new WebAuthnError({ |
| 81 | message: "Discoverable credentials were required but no available authenticator supported it", |
| 82 | code: "ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT", |
| 83 | cause: error |
| 84 | }); |
| 85 | } else if (options.mediation === "conditional" && publicKey.authenticatorSelection?.userVerification === "required") { |
| 86 | return new WebAuthnError({ |
| 87 | message: "User verification was required during automatic registration but it could not be performed", |
| 88 | code: "ERROR_AUTO_REGISTER_USER_VERIFICATION_FAILURE", |
| 89 | cause: error |
| 90 | }); |
| 91 | } else if (publicKey.authenticatorSelection?.userVerification === "required") { |
| 92 | return new WebAuthnError({ |
| 93 | message: "User verification was required but no available authenticator supported it", |
| 94 | code: "ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT", |
| 95 | cause: error |
| 96 | }); |
| 97 | } |
| 98 | } else if (error.name === "InvalidStateError") { |
| 99 | return new WebAuthnError({ |
| 100 | message: "The authenticator was previously registered", |
| 101 | code: "ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED", |
| 102 | cause: error |
| 103 | }); |
| 104 | } else if (error.name === "NotAllowedError") { |
| 105 | return new WebAuthnError({ |
| 106 | message: error.message, |
| 107 | code: "ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY", |
| 108 | cause: error |
| 109 | }); |
| 110 | } else if (error.name === "NotSupportedError") { |
| 111 | const validPubKeyCredParams = publicKey.pubKeyCredParams.filter((param) => param.type === "public-key"); |
| 112 | if (validPubKeyCredParams.length === 0) { |
| 113 | return new WebAuthnError({ |
| 114 | message: 'No entry in pubKeyCredParams was of type "public-key"', |
| 115 | code: "ERROR_MALFORMED_PUBKEYCREDPARAMS", |
| 116 | cause: error |
| 117 | }); |
| 118 | } |
| 119 | return new WebAuthnError({ |
| 120 | message: "No available authenticator supported any of the specified pubKeyCredParams algorithms", |
| 121 | code: "ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG", |
| 122 | cause: error |
| 123 | }); |
| 124 | } else if (error.name === "SecurityError") { |
| 125 | const effectiveDomain = globalThis.location.hostname; |
| 126 | if (!isValidDomain(effectiveDomain)) { |
| 127 | return new WebAuthnError({ |
| 128 | message: `${globalThis.location.hostname} is an invalid domain`, |
| 129 | code: "ERROR_INVALID_DOMAIN", |
| 130 | cause: error |
| 131 | }); |
| 132 | } else if (publicKey.rp.id !== effectiveDomain) { |
| 133 | return new WebAuthnError({ |
| 134 | message: `The RP ID "${publicKey.rp.id}" is invalid for this domain`, |
| 135 | code: "ERROR_INVALID_RP_ID", |
| 136 | cause: error |
| 137 | }); |
| 138 | } |
| 139 | } else if (error.name === "TypeError") { |
| 140 | if (publicKey.user.id.byteLength < 1 || publicKey.user.id.byteLength > 64) { |
| 141 | return new WebAuthnError({ |
| 142 | message: "User ID was not between 1 and 64 characters", |
| 143 | code: "ERROR_INVALID_USER_ID_LENGTH", |
| 144 | cause: error |
| 145 | }); |
| 146 | } |
| 147 | } else if (error.name === "UnknownError") { |
| 148 | return new WebAuthnError({ |
| 149 | message: "The authenticator was unable to process the specified options, or could not create a new credential", |
| 150 | code: "ERROR_AUTHENTICATOR_GENERAL_ERROR", |
| 151 | cause: error |
| 152 | }); |
| 153 | } |
| 154 | return error; |
| 155 | } |
| 156 | |
| 157 | // node_modules/@simplewebauthn/browser/esm/helpers/webAuthnAbortService.js |
| 158 | class BaseWebAuthnAbortService { |
| 159 | constructor() { |
| 160 | Object.defineProperty(this, "controller", { |
| 161 | enumerable: true, |
| 162 | configurable: true, |
| 163 | writable: true, |
| 164 | value: undefined |
| 165 | }); |
| 166 | } |
| 167 | createNewAbortSignal() { |
| 168 | if (this.controller) { |
| 169 | const abortError = new Error("Cancelling existing WebAuthn API call for new one"); |
| 170 | abortError.name = "AbortError"; |
| 171 | this.controller.abort(abortError); |
| 172 | } |
| 173 | const newController = new AbortController; |
| 174 | this.controller = newController; |
| 175 | return newController.signal; |
| 176 | } |
| 177 | cancelCeremony() { |
| 178 | if (this.controller) { |
| 179 | const abortError = new Error("Manually cancelling existing WebAuthn API call"); |
| 180 | abortError.name = "AbortError"; |
| 181 | this.controller.abort(abortError); |
| 182 | this.controller = undefined; |
| 183 | } |
| 184 | } |
| 185 | } |
| 186 | var WebAuthnAbortService = new BaseWebAuthnAbortService; |
| 187 | |
| 188 | // node_modules/@simplewebauthn/browser/esm/helpers/toAuthenticatorAttachment.js |
| 189 | var attachments = ["cross-platform", "platform"]; |
| 190 | function toAuthenticatorAttachment(attachment) { |
| 191 | if (!attachment) { |
| 192 | return; |
| 193 | } |
| 194 | if (attachments.indexOf(attachment) < 0) { |
| 195 | return; |
| 196 | } |
| 197 | return attachment; |
| 198 | } |
| 199 | |
| 200 | // node_modules/@simplewebauthn/browser/esm/methods/startRegistration.js |
| 201 | async function startRegistration(options) { |
| 202 | if (!options.optionsJSON && options.challenge) { |
| 203 | console.warn("startRegistration() was not called correctly. It will try to continue with the provided options, but this call should be refactored to use the expected call structure instead. See https://simplewebauthn.dev/docs/packages/browser#typeerror-cannot-read-properties-of-undefined-reading-challenge for more information."); |
| 204 | options = { optionsJSON: options }; |
| 205 | } |
| 206 | const { optionsJSON, useAutoRegister = false } = options; |
| 207 | if (!browserSupportsWebAuthn()) { |
| 208 | throw new Error("WebAuthn is not supported in this browser"); |
| 209 | } |
| 210 | const publicKey = { |
| 211 | ...optionsJSON, |
| 212 | challenge: base64URLStringToBuffer(optionsJSON.challenge), |
| 213 | user: { |
| 214 | ...optionsJSON.user, |
| 215 | id: base64URLStringToBuffer(optionsJSON.user.id) |
| 216 | }, |
| 217 | excludeCredentials: optionsJSON.excludeCredentials?.map(toPublicKeyCredentialDescriptor) |
| 218 | }; |
| 219 | const createOptions = {}; |
| 220 | if (useAutoRegister) { |
| 221 | createOptions.mediation = "conditional"; |
| 222 | } |
| 223 | createOptions.publicKey = publicKey; |
| 224 | createOptions.signal = WebAuthnAbortService.createNewAbortSignal(); |
| 225 | let credential; |
| 226 | try { |
| 227 | credential = await navigator.credentials.create(createOptions); |
| 228 | } catch (err) { |
| 229 | throw identifyRegistrationError({ error: err, options: createOptions }); |
| 230 | } |
| 231 | if (!credential) { |
| 232 | throw new Error("Registration was not completed"); |
| 233 | } |
| 234 | const { id, rawId, response, type } = credential; |
| 235 | let transports = undefined; |
| 236 | if (typeof response.getTransports === "function") { |
| 237 | transports = response.getTransports(); |
| 238 | } |
| 239 | let responsePublicKeyAlgorithm = undefined; |
| 240 | if (typeof response.getPublicKeyAlgorithm === "function") { |
| 241 | try { |
| 242 | responsePublicKeyAlgorithm = response.getPublicKeyAlgorithm(); |
| 243 | } catch (error) { |
| 244 | warnOnBrokenImplementation("getPublicKeyAlgorithm()", error); |
| 245 | } |
| 246 | } |
| 247 | let responsePublicKey = undefined; |
| 248 | if (typeof response.getPublicKey === "function") { |
| 249 | try { |
| 250 | const _publicKey = response.getPublicKey(); |
| 251 | if (_publicKey !== null) { |
| 252 | responsePublicKey = bufferToBase64URLString(_publicKey); |
| 253 | } |
| 254 | } catch (error) { |
| 255 | warnOnBrokenImplementation("getPublicKey()", error); |
| 256 | } |
| 257 | } |
| 258 | let responseAuthenticatorData; |
| 259 | if (typeof response.getAuthenticatorData === "function") { |
| 260 | try { |
| 261 | responseAuthenticatorData = bufferToBase64URLString(response.getAuthenticatorData()); |
| 262 | } catch (error) { |
| 263 | warnOnBrokenImplementation("getAuthenticatorData()", error); |
| 264 | } |
| 265 | } |
| 266 | return { |
| 267 | id, |
| 268 | rawId: bufferToBase64URLString(rawId), |
| 269 | response: { |
| 270 | attestationObject: bufferToBase64URLString(response.attestationObject), |
| 271 | clientDataJSON: bufferToBase64URLString(response.clientDataJSON), |
| 272 | transports, |
| 273 | publicKeyAlgorithm: responsePublicKeyAlgorithm, |
| 274 | publicKey: responsePublicKey, |
| 275 | authenticatorData: responseAuthenticatorData |
| 276 | }, |
| 277 | type, |
| 278 | clientExtensionResults: credential.getClientExtensionResults(), |
| 279 | authenticatorAttachment: toAuthenticatorAttachment(credential.authenticatorAttachment) |
| 280 | }; |
| 281 | } |
| 282 | function warnOnBrokenImplementation(methodName, cause) { |
| 283 | console.warn(`The browser extension that intercepted this WebAuthn API call incorrectly implemented ${methodName}. You should report this error to them. |
| 284 | `, cause); |
| 285 | } |
| 286 | // node_modules/@simplewebauthn/browser/esm/helpers/browserSupportsWebAuthnAutofill.js |
| 287 | function browserSupportsWebAuthnAutofill() { |
| 288 | if (!browserSupportsWebAuthn()) { |
| 289 | return _browserSupportsWebAuthnAutofillInternals.stubThis(new Promise((resolve) => resolve(false))); |
| 290 | } |
| 291 | const globalPublicKeyCredential = globalThis.PublicKeyCredential; |
| 292 | if (globalPublicKeyCredential?.isConditionalMediationAvailable === undefined) { |
| 293 | return _browserSupportsWebAuthnAutofillInternals.stubThis(new Promise((resolve) => resolve(false))); |
| 294 | } |
| 295 | return _browserSupportsWebAuthnAutofillInternals.stubThis(globalPublicKeyCredential.isConditionalMediationAvailable()); |
| 296 | } |
| 297 | var _browserSupportsWebAuthnAutofillInternals = { |
| 298 | stubThis: (value) => value |
| 299 | }; |
| 300 | |
| 301 | // node_modules/@simplewebauthn/browser/esm/helpers/identifyAuthenticationError.js |
| 302 | function identifyAuthenticationError({ error, options }) { |
| 303 | const { publicKey } = options; |
| 304 | if (!publicKey) { |
| 305 | throw Error("options was missing required publicKey property"); |
| 306 | } |
| 307 | if (error.name === "AbortError") { |
| 308 | if (options.signal instanceof AbortSignal) { |
| 309 | return new WebAuthnError({ |
| 310 | message: "Authentication ceremony was sent an abort signal", |
| 311 | code: "ERROR_CEREMONY_ABORTED", |
| 312 | cause: error |
| 313 | }); |
| 314 | } |
| 315 | } else if (error.name === "NotAllowedError") { |
| 316 | return new WebAuthnError({ |
| 317 | message: error.message, |
| 318 | code: "ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY", |
| 319 | cause: error |
| 320 | }); |
| 321 | } else if (error.name === "SecurityError") { |
| 322 | const effectiveDomain = globalThis.location.hostname; |
| 323 | if (!isValidDomain(effectiveDomain)) { |
| 324 | return new WebAuthnError({ |
| 325 | message: `${globalThis.location.hostname} is an invalid domain`, |
| 326 | code: "ERROR_INVALID_DOMAIN", |
| 327 | cause: error |
| 328 | }); |
| 329 | } else if (publicKey.rpId !== effectiveDomain) { |
| 330 | return new WebAuthnError({ |
| 331 | message: `The RP ID "${publicKey.rpId}" is invalid for this domain`, |
| 332 | code: "ERROR_INVALID_RP_ID", |
| 333 | cause: error |
| 334 | }); |
| 335 | } |
| 336 | } else if (error.name === "UnknownError") { |
| 337 | return new WebAuthnError({ |
| 338 | message: "The authenticator was unable to process the specified options, or could not create a new assertion signature", |
| 339 | code: "ERROR_AUTHENTICATOR_GENERAL_ERROR", |
| 340 | cause: error |
| 341 | }); |
| 342 | } |
| 343 | return error; |
| 344 | } |
| 345 | |
| 346 | // node_modules/@simplewebauthn/browser/esm/methods/startAuthentication.js |
| 347 | async function startAuthentication(options) { |
| 348 | if (!options.optionsJSON && options.challenge) { |
| 349 | console.warn("startAuthentication() was not called correctly. It will try to continue with the provided options, but this call should be refactored to use the expected call structure instead. See https://simplewebauthn.dev/docs/packages/browser#typeerror-cannot-read-properties-of-undefined-reading-challenge for more information."); |
| 350 | options = { optionsJSON: options }; |
| 351 | } |
| 352 | const { optionsJSON, useBrowserAutofill = false, verifyBrowserAutofillInput = true } = options; |
| 353 | if (!browserSupportsWebAuthn()) { |
| 354 | throw new Error("WebAuthn is not supported in this browser"); |
| 355 | } |
| 356 | let allowCredentials; |
| 357 | if (optionsJSON.allowCredentials?.length !== 0) { |
| 358 | allowCredentials = optionsJSON.allowCredentials?.map(toPublicKeyCredentialDescriptor); |
| 359 | } |
| 360 | const publicKey = { |
| 361 | ...optionsJSON, |
| 362 | challenge: base64URLStringToBuffer(optionsJSON.challenge), |
| 363 | allowCredentials |
| 364 | }; |
| 365 | const getOptions = {}; |
| 366 | if (useBrowserAutofill) { |
| 367 | if (!await browserSupportsWebAuthnAutofill()) { |
| 368 | throw Error("Browser does not support WebAuthn autofill"); |
| 369 | } |
| 370 | const eligibleInputs = document.querySelectorAll("input[autocomplete$='webauthn']"); |
| 371 | if (eligibleInputs.length < 1 && verifyBrowserAutofillInput) { |
| 372 | throw Error('No <input> with "webauthn" as the only or last value in its `autocomplete` attribute was detected'); |
| 373 | } |
| 374 | getOptions.mediation = "conditional"; |
| 375 | publicKey.allowCredentials = []; |
| 376 | } |
| 377 | getOptions.publicKey = publicKey; |
| 378 | getOptions.signal = WebAuthnAbortService.createNewAbortSignal(); |
| 379 | let credential; |
| 380 | try { |
| 381 | credential = await navigator.credentials.get(getOptions); |
| 382 | } catch (err) { |
| 383 | throw identifyAuthenticationError({ error: err, options: getOptions }); |
| 384 | } |
| 385 | if (!credential) { |
| 386 | throw new Error("Authentication was not completed"); |
| 387 | } |
| 388 | const { id, rawId, response, type } = credential; |
| 389 | let userHandle = undefined; |
| 390 | if (response.userHandle) { |
| 391 | userHandle = bufferToBase64URLString(response.userHandle); |
| 392 | } |
| 393 | return { |
| 394 | id, |
| 395 | rawId: bufferToBase64URLString(rawId), |
| 396 | response: { |
| 397 | authenticatorData: bufferToBase64URLString(response.authenticatorData), |
| 398 | clientDataJSON: bufferToBase64URLString(response.clientDataJSON), |
| 399 | signature: bufferToBase64URLString(response.signature), |
| 400 | userHandle |
| 401 | }, |
| 402 | type, |
| 403 | clientExtensionResults: credential.getClientExtensionResults(), |
| 404 | authenticatorAttachment: toAuthenticatorAttachment(credential.authenticatorAttachment) |
| 405 | }; |
| 406 | } |
| 407 | // node_modules/@simplewebauthn/browser/esm/helpers/platformAuthenticatorIsAvailable.js |
| 408 | function platformAuthenticatorIsAvailable() { |
| 409 | if (!browserSupportsWebAuthn()) { |
| 410 | return new Promise((resolve) => resolve(false)); |
| 411 | } |
| 412 | return PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(); |
| 413 | } |
| 414 | export { |
| 415 | WebAuthnAbortService, |
| 416 | WebAuthnError, |
| 417 | _browserSupportsWebAuthnAutofillInternals, |
| 418 | _browserSupportsWebAuthnInternals, |
| 419 | base64URLStringToBuffer, |
| 420 | browserSupportsWebAuthn, |
| 421 | browserSupportsWebAuthnAutofill, |
| 422 | bufferToBase64URLString, |
| 423 | platformAuthenticatorIsAvailable, |
| 424 | startAuthentication, |
| 425 | startRegistration |
| 426 | }; |
| 427 |