Browse Source

refactor(js): enable touch handlers only on touch devices and fix various issues in WebAuthnHandler

Frédéric Guillot 8 months ago
parent
commit
52c1386450
2 changed files with 43 additions and 16 deletions
  1. 9 1
      internal/ui/static/js/touch_handler.js
  2. 34 15
      internal/ui/static/js/webauthn_handler.js

+ 9 - 1
internal/ui/static/js/touch_handler.js

@@ -47,7 +47,7 @@ class TouchHandler {
     }
 
     onItemTouchMove(event) {
-        if (event.touches === undefined || event.touches.length !== 1 || this.element === null) {
+        if (event.touches === undefined || event.touches.length !== 1 || this.touch.element === null) {
             return;
         }
 
@@ -151,7 +151,15 @@ class TouchHandler {
         }
     }
 
+    static isTouchSupported() {
+        return "ontouchstart" in window || navigator.maxTouchPoints > 0;
+    }
+
     listen() {
+        if (!TouchHandler.isTouchSupported()) {
+            return;
+        }
+
         const eventListenerOptions = { passive: true };
 
         document.querySelectorAll(".entry-swipe").forEach((element) => {

+ 34 - 15
internal/ui/static/js/webauthn_handler.js

@@ -1,16 +1,15 @@
 class WebAuthnHandler {
     static isWebAuthnSupported() {
-        return window.PublicKeyCredential;
+        return typeof PublicKeyCredential !== "undefined";
     }
 
     static showErrorMessage(errorMessage) {
-        console.log("webauthn error: " + errorMessage);
+        console.error("WebAuthn error:", errorMessage);
 
         const alertElement = document.getElementById("webauthn-error-alert");
-        if (!alertElement) {
-            return;
+        if (alertElement) {
+            alertElement.remove();
         }
-        alertElement.remove();
 
         const alertTemplateElement = document.getElementById("webauthn-error");
         if (alertTemplateElement) {
@@ -23,15 +22,15 @@ class WebAuthnHandler {
         }
     }
 
-    async isConditionalLoginSupported() {
+    static async isConditionalLoginSupported() {
         return WebAuthnHandler.isWebAuthnSupported() &&
             window.PublicKeyCredential.isConditionalMediationAvailable &&
-            window.PublicKeyCredential.isConditionalMediationAvailable();
+            await window.PublicKeyCredential.isConditionalMediationAvailable();
     }
 
     async conditionalLogin(abortController) {
-        if (await this.isConditionalLoginSupported()) {
-            this.login("", abortController);
+        if (await WebAuthnHandler.isConditionalLoginSupported()) {
+            return this.login("", abortController);
         }
     }
 
@@ -49,7 +48,7 @@ class WebAuthnHandler {
     async post(urlKey, username, data) {
         let url = document.body.dataset[urlKey];
         if (username) {
-            url += "?username=" + username;
+            url += "?username=" + encodeURIComponent(username);
         }
 
         return sendPOSTRequest(url, data);
@@ -58,7 +57,7 @@ class WebAuthnHandler {
     async get(urlKey, username) {
         let url = document.body.dataset[urlKey];
         if (username) {
-            url += "?username=" + username;
+            url += "?username=" + encodeURIComponent(username);
         }
         return fetch(url);
     }
@@ -83,14 +82,27 @@ class WebAuthnHandler {
             return;
         }
 
-        const credentialCreationOptions = await registerBeginResponse.json();
+        let credentialCreationOptions;
+        try {
+            credentialCreationOptions = await registerBeginResponse.json();
+        } catch (err) {
+            WebAuthnHandler.showErrorMessage("Failed to parse registration options");
+            return;
+        }
+
         credentialCreationOptions.publicKey.challenge = this.decodeBuffer(credentialCreationOptions.publicKey.challenge);
         credentialCreationOptions.publicKey.user.id = this.decodeBuffer(credentialCreationOptions.publicKey.user.id);
         if (Object.hasOwn(credentialCreationOptions.publicKey, 'excludeCredentials')) {
             credentialCreationOptions.publicKey.excludeCredentials.forEach((credential) => credential.id = this.decodeBuffer(credential.id));
         }
 
-        const attestation = await navigator.credentials.create(credentialCreationOptions);
+        let attestation;
+        try {
+            attestation = await navigator.credentials.create(credentialCreationOptions);
+        } catch (err) {
+            WebAuthnHandler.showErrorMessage(err);
+            return;
+        }
 
         let registrationFinishResponse;
         try {
@@ -109,7 +121,7 @@ class WebAuthnHandler {
         }
 
         if (!registrationFinishResponse.ok) {
-            throw new Error("Login failed with HTTP status code " + response.status);
+            throw new Error("Registration failed with HTTP status code " + registrationFinishResponse.status);
         }
 
         const jsonData = await registrationFinishResponse.json();
@@ -125,7 +137,14 @@ class WebAuthnHandler {
             return;
         }
 
-        const credentialRequestOptions = await loginBeginResponse.json();
+        let credentialRequestOptions;
+        try {
+            credentialRequestOptions = await loginBeginResponse.json();
+        } catch (err) {
+            WebAuthnHandler.showErrorMessage("Failed to parse login options");
+            return;
+        }
+
         credentialRequestOptions.publicKey.challenge = this.decodeBuffer(credentialRequestOptions.publicKey.challenge);
 
         if (Object.hasOwn(credentialRequestOptions.publicKey, 'allowCredentials')) {