diff --git a/internal/ui/static/js/touch_handler.js b/internal/ui/static/js/touch_handler.js index c9f0aaed..aa314f23 100644 --- a/internal/ui/static/js/touch_handler.js +++ b/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) => { diff --git a/internal/ui/static/js/webauthn_handler.js b/internal/ui/static/js/webauthn_handler.js index 79171a82..ed5336a7 100644 --- a/internal/ui/static/js/webauthn_handler.js +++ b/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')) {