| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 | /* * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */"use strict";import base64url from "./base64url.js";import http from "./http.js";import abortController from "./abort-controller.js";async function isConditionalMediationAvailable() {  return !!(    window.PublicKeyCredential &&    window.PublicKeyCredential.isConditionalMediationAvailable &&    (await window.PublicKeyCredential.isConditionalMediationAvailable())  );}async function authenticate(headers, contextPath, useConditionalMediation) {  let options;  try {    const optionsResponse = await http.post(`${contextPath}/webauthn/authenticate/options`, headers);    if (!optionsResponse.ok) {      throw new Error(`HTTP ${optionsResponse.status}`);    }    options = await optionsResponse.json();  } catch (err) {    throw new Error(`Authentication failed. Could not fetch authentication options: ${err.message}`, { cause: err });  }  // FIXME: Use https://www.w3.org/TR/webauthn-3/#sctn-parseRequestOptionsFromJSON  const decodedOptions = {    ...options,    challenge: base64url.decode(options.challenge),  };  // Invoke the WebAuthn get() method.  const credentialOptions = {    publicKey: decodedOptions,    signal: abortController.newSignal(),  };  if (useConditionalMediation) {    // Request a conditional UI    credentialOptions.mediation = "conditional";  }  let cred;  try {    cred = await navigator.credentials.get(credentialOptions);  } catch (err) {    throw new Error(`Authentication failed. Call to navigator.credentials.get failed: ${err.message}`, { cause: err });  }  const { response, type: credType } = cred;  let userHandle;  if (response.userHandle) {    userHandle = base64url.encode(response.userHandle);  }  const body = {    id: cred.id,    rawId: base64url.encode(cred.rawId),    response: {      authenticatorData: base64url.encode(response.authenticatorData),      clientDataJSON: base64url.encode(response.clientDataJSON),      signature: base64url.encode(response.signature),      userHandle,    },    credType,    clientExtensionResults: cred.getClientExtensionResults(),    authenticatorAttachment: cred.authenticatorAttachment,  };  let authenticationResponse;  try {    const authenticationCallResponse = await http.post(`${contextPath}/login/webauthn`, headers, body);    if (!authenticationCallResponse.ok) {      throw new Error(`HTTP ${authenticationCallResponse.status}`);    }    authenticationResponse = await authenticationCallResponse.json();    //   if (authenticationResponse && authenticationResponse.authenticated) {  } catch (err) {    throw new Error(`Authentication failed. Could not process the authentication request: ${err.message}`, {      cause: err,    });  }  if (!(authenticationResponse && authenticationResponse.authenticated && authenticationResponse.redirectUrl)) {    throw new Error(      `Authentication failed. Expected {"authenticated": true, "redirectUrl": "..."}, server responded with: ${JSON.stringify(authenticationResponse)}`,    );  }  return authenticationResponse.redirectUrl;}async function register(headers, contextPath, label) {  if (!label) {    throw new Error("Error: Passkey Label is required");  }  let options;  try {    const optionsResponse = await http.post(`${contextPath}/webauthn/register/options`, headers);    if (!optionsResponse.ok) {      throw new Error(`Server responded with HTTP ${optionsResponse.status}`);    }    options = await optionsResponse.json();  } catch (e) {    throw new Error(`Registration failed. Could not fetch registration options: ${e.message}`, { cause: e });  }  // FIXME: Use https://www.w3.org/TR/webauthn-3/#sctn-parseCreationOptionsFromJSON  const decodedExcludeCredentials = !options.excludeCredentials    ? []    : options.excludeCredentials.map((cred) => ({        ...cred,        id: base64url.decode(cred.id),      }));  const decodedOptions = {    ...options,    user: {      ...options.user,      id: base64url.decode(options.user.id),    },    challenge: base64url.decode(options.challenge),    excludeCredentials: decodedExcludeCredentials,  };  let credentialsContainer;  try {    credentialsContainer = await navigator.credentials.create({      publicKey: decodedOptions,      signal: abortController.newSignal(),    });  } catch (e) {    throw new Error(`Registration failed. Call to navigator.credentials.create failed: ${e.message}`, { cause: e });  }  // FIXME: Let response be credential.response. If response is not an instance of AuthenticatorAttestationResponse, abort the ceremony with a user-visible error. https://www.w3.org/TR/webauthn-3/#sctn-registering-a-new-credential  const { response } = credentialsContainer;  const credential = {    id: credentialsContainer.id,    rawId: base64url.encode(credentialsContainer.rawId),    response: {      attestationObject: base64url.encode(response.attestationObject),      clientDataJSON: base64url.encode(response.clientDataJSON),      transports: response.getTransports ? response.getTransports() : [],    },    type: credentialsContainer.type,    clientExtensionResults: credentialsContainer.getClientExtensionResults(),    authenticatorAttachment: credentialsContainer.authenticatorAttachment,  };  const registrationRequest = {    publicKey: {      credential: credential,      label: label,    },  };  let verificationJSON;  try {    const verificationResp = await http.post(`${contextPath}/webauthn/register`, headers, registrationRequest);    if (!verificationResp.ok) {      throw new Error(`HTTP ${verificationResp.status}`);    }    verificationJSON = await verificationResp.json();  } catch (e) {    throw new Error(`Registration failed. Could not process the registration request: ${e.message}`, { cause: e });  }  if (!(verificationJSON && verificationJSON.success)) {    throw new Error(`Registration failed. Server responded with: ${JSON.stringify(verificationJSON)}`);  }}export default {  authenticate,  register,  isConditionalMediationAvailable,};
 |