Project import generated by Copybara.

GitOrigin-RevId: 63746295f1a5ab5a619056791995793d65529e62
diff --git a/src/js/secondfactor.js b/src/js/secondfactor.js
new file mode 100644
index 0000000..f537146
--- /dev/null
+++ b/src/js/secondfactor.js
@@ -0,0 +1,107 @@
+function verify() {
+  if (!document.getElementById("code").checkValidity()) {
+    document.querySelector(".mdl-js-snackbar").MaterialSnackbar.showSnackbar({
+      message: "El código de verificación debe tener 6 cifras."
+    });
+
+    return;
+  }
+
+  var body = {
+    code: document.getElementById("code").value
+  };
+
+  var content = document.getElementById("content");
+  content.innerHTML = '<div class="mdl-spinner mdl-js-spinner is-active"></div>';
+  content.style.textAlign = "center";
+  componentHandler.upgradeElements(content);
+
+  fetch("ajax/verifysecuritycode.php", {
+    method: "POST",
+    headers: {
+      "Content-Type": "application/json"
+    },
+    body: JSON.stringify(body)
+  }).then(response => {
+    if (response.status !== 200) {
+      throw new Error("HTTP status is not 200.");
+    }
+
+    return response.json();
+  }).then(response => {
+    switch (response.status) {
+      case "ok":
+      document.location = "index.php";
+      break;
+
+      case "wrongCode":
+      document.location = "index.php?msg=secondfactorwrongcode";
+      break;
+
+      default:
+      console.error("An unknown status code was returned.");
+    }
+  }).catch(err => console.error("An unexpected error occurred.", err));
+}
+
+function verifyKeypress(e) {
+  if (event.keyCode == 13) {
+    verify();
+  }
+}
+
+function startWebauthn() {
+  fetch("ajax/startwebauthnauthentication.php", {
+    method: "POST"
+  }).then(response => {
+    if (response.status !== 200) {
+      response.text(); // @TODO: Remove this. It is only used so the response is available in Chrome Dev Tools
+      throw new Error("HTTP status is not 200.");
+    }
+
+    return response.json();
+  }).then(response => {
+    recursiveBase64StrToArrayBuffer(response);
+    return response;
+  }).then(getCredentialArgs => {
+    return navigator.credentials.get(getCredentialArgs);
+  }).then(cred => {
+    return {
+      id: cred.rawId ? arrayBufferToBase64(cred.rawId) : null,
+      clientDataJSON: cred.response.clientDataJSON  ? arrayBufferToBase64(cred.response.clientDataJSON) : null,
+      authenticatorData: cred.response.authenticatorData ? arrayBufferToBase64(cred.response.authenticatorData) : null,
+      signature : cred.response.signature ? arrayBufferToBase64(cred.response.signature) : null
+    };
+  }).then(JSON.stringify).then(AuthenticatorAttestationResponse => {
+    return window.fetch("ajax/completewebauthnauthentication.php", {
+      method: "POST",
+      body: AuthenticatorAttestationResponse,
+    });
+  }).then(response => {
+    if (response.status !== 200) {
+      response.text(); // @TODO: remove this. It is only used so the response is available in Chrome Dev Tools
+      throw new Error("HTTP status is not 200 (2).");
+    }
+
+    return response.json();
+  }).then(json => {
+    if (json.status == "ok") {
+      document.location = "index.php";
+    }
+  }).catch(err => console.error("An unexpected error occurred.", err));
+}
+
+window.addEventListener("load", function() {
+  if (document.getElementById("totp")) {
+    document.getElementById("verify").addEventListener("click", verify);
+    document.getElementById("code").addEventListener("keypress", verifyKeypress);
+    document.getElementById("code").focus();
+    document.querySelector("a[href=\"#totp\"]").addEventListener("click", _ => {
+      document.getElementById("code").focus();
+    });
+  }
+
+  if (document.getElementById("startwebauthn")) {
+    document.getElementById("startwebauthn").addEventListener("click", startWebauthn);
+  }
+});